본문으로 바로가기

처음으로 안드로이드 프로그래밍을 하다보니 이만저만 고생을 하고 있다.

간단한 소켓 통신 하는데도, 인터넷 소스를 보고 똑같이 코딩해도 이상한 에러가 나면서

개발한 앱이 정상동작을 하지 않는다.

 

[안드로이드 개발 요약 - 4] 간단한 TCP 소켓통신 샘플과 주의사항

 

안드로이드는 OS 버전에 따라 구현 방식도 바뀌어야 하기 때문에 어렵구나라는것을 느낀다.

아래 소스는 단순히 문자열 기반으로 C# 으로 개발된 소켓 서버에 문자열을 보내는 예제다.

 

//MainActivity.java의 풀 소스

package com.alzio.apnotice_alzio;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.StrictMode;
import android.support.annotation.MainThread;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import static android.os.StrictMode.*;

public class MainActivity extends AppCompatActivity {

    private Socket apConnSocket = null;
    private BufferedReader sockReader;
    private BufferedWriter sockWriter;
    private PrintWriter sockPrintWriter;
    SocketThread thrSockConn;
    String userPhone;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //ThreadPolicy  //*********************** 주의사항 #1
        //안드로이드 OS 최신 버전에서는 메인쓰레드내에서 소켓을 사용할 수 없게 막아놔서
        //권한 관련 하여(아래 내용이 뭔지도 모르지만 저걸 해줘야 돌아간다고 해서 해봤더니 되긴 됨)
        //소켓 통신 부분을 별도의 쓰레드로 빼지 않을 때에는 아래의 코드를 써주면 해결이됨.
        //참고로 안드로이드 에뮬레이터인 AVD에서는 정상적인 소켓 통신이 되었으나,
        //내가 실사용 중인 스마트폰에서는 소켓에 데이터를 전송하면 앱이 죽어버렸음

        ThreadPolicy policy = new ThreadPolicy.Builder().permitAll().build();
        setThreadPolicy(policy);

        //TelephonyManager telManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        //userPhone = telManager.getLine1Number();

        Button btn =  (Button)findViewById(R.id.btnInputSend);

        final TextView tvSendText =  (TextView)findViewById(R.id.edtSendText);
        final Button btnSMSError = (Button)findViewById(R.id.btnDefaultSMSErrorSend);
        final Button btnOllehMapError = (Button)findViewById(R.id.btnDefaultOllehMapError);


        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                String sendMsg = "[패킷]`" + "`" + tvSendText.getText().toString();
                sendServer(sendMsg);
                Toast.makeText(getApplicationContext(), sendMsg + " 전송", Toast.LENGTH_SHORT).show();
                SendMsgAlert();
            }
        });


        btnSMSError.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String sendMsg = "[패킷]`MobileNotice`" + btnSMSError.getText().toString();
                sendServer(sendMsg);
                Toast.makeText(getApplicationContext(), sendMsg + " 전송", Toast.LENGTH_SHORT).show();
                SendMsgAlert();
            }
        });



        btnOllehMapError.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String sendMsg = "[패킷]`MobileNotice`" + btnOllehMapError.getText().toString();
                sendServer(sendMsg);
                Toast.makeText(getApplicationContext(), sendMsg + " 전송", Toast.LENGTH_SHORT).show();
                SendMsgAlert();
            }
        });


        thrSockConn = new SocketThread(); //*********************** 주의사항 #2
        thrSockConn.setDaemon(true);
        thrSockConn.start();

        //if(apConnSocket != null && apConnSocket.isConnected())
        //{
            //Toast.makeText(getApplicationContext(), "서버 연결 성공", Toast.LENGTH_SHORT).show();
        //}
        //else
        //{
            //Toast.makeText(getApplicationContext(), "서버 연결 실패", Toast.LENGTH_SHORT).show();
        //}
    }

    private void sendServer(String msg)
    {
        sockPrintWriter.println(msg);
    }

    private void SendMsgAlert()
    {
        AlertDialog.Builder alert = new AlertDialog.Builder(MainActivity.this);

        alert.setPositiveButton("확인", new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
                if(apConnSocket.isConnected()) {
                    try {
                        apConnSocket.close();
                    }catch(IOException ioe)
                    {

                    }
                }
                moveTaskToBack(true);
                finish();
                android.os.Process.killProcess(android.os.Process.myPid());

            }

        });

        alert.setMessage("과도한 공지를 제한하기 위해 1회 발송 후 앱을 종료합니다.");
        alert.show();
    }

    class SocketThread extends Thread{
        public void run(){
                String ipaddr = "122.163.25.20";

                int ipport = 12345;
                try {
                    apConnSocket = new Socket(ipaddr, ipport);

                    sockWriter = new BufferedWriter(new OutputStreamWriter(apConnSocket.getOutputStream(), "EUC-KR"));

 

//tcp 소켓 서버는 C#으로 개발되어있고, 앱은 자바인데

//내가 알기론 둘다 UTF-8이 기본으로 알고있는데 한글 깨지는 문제가 발생해서 진짜

//이거 해결하느라 한참을 해맸다.

//소켓에서 출력 스트림을 다른 출력객체로 변환시에 euc-kr로 캐릭터셋을 변경해주니

//c#으로 개발된 서버에 정상적으로 한글을 보낼 수 있었다.

//미국인이고 싶다.

 

sockReader = new BufferedReader(new InputStreamReader(apConnSocket.getInputStream())); sockPrintWriter = new PrintWriter(sockWriter, true); } catch (UnknownHostException ue) { System.out.println(ue); ue.printStackTrace(); } catch (IOException ie) { System.out.println(ie); ie.printStackTrace(); } } } }

 

일단 소켓 통신을 하면서 고생했던 부분에 대한 주의사항을 정리하자면, 

 

1. onCreate() 안에서 소켓을 생성하는 거 자체가 최신 안드로이드 OS에서는 금지가 된 조항이다.

 

그래서 소켓 생성하는 부분은 별도의 쓰레드로 빼야한다. 그래서 별도의 쓰레드 클래스로 빼고, 소켓을 생성했다.

 

2. 마찬가지로 메인 쓰레드에서 소켓에 접근하는 것도 조심해야 한다.

   소켓을 전역으로 선언했으나, 익명메소드에서 호출시 Looper.java(메세지 처리) 하는 부분에서 

   오류가 나면서 프로그램이 그냥 뻗는다. 그래서 주의사항 #1 처럼 ThreadPolicy로 쓰레드 권한을 줘야한다.

 

3. 안드로이드에는 앱에 권한이라는게 존재해서 특정권한을 허용해줘야 사용할 수 있는 것들이 있다.

   권한은 AndroidManifest.xml 파일에 추가 해줘야 한다.

 

    <uses-permission android:name="android.permission.INTERNET"> //소켓 통신을 하기위해서 꼭 필요한 권한

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"> //소켓 통신을 하기위한 부수적 권한

    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE">//소켓 통신을 하기위한 부수적 권한

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE">//소켓 통신을 하기위한 부수적 권한

    <uses-permission android:name="android.permission.READ_PHONE_STATE">//스마트폰의 번호를 얻어오기 위한 권한

    <uses-permission android:name="android.permission.READ_CONTACTS">//연락처 접근 권한 

 

4. PC가 서버이고 AVD 에뮬레이터가 같은 PC에서 통신을 한다고 해서 localhost 아이피인 127.0.0.1로 통신한다고

   통신이 되지 않는다. PC의 실제 IP로 통신을 해야 한다.

 

   인터넷 검색 열라 했더니 AVD 는 10.0.2.2 로 접속해야 한다라는데 안되드라.

   그냥 자기 PC 에서 ipconfig 해서 나오는 ip주소로 접속하면 된다.

 

5. 권한 및 AVD 아이피등을 해도 안된다면 adb를 통해 포트를 열어야 할 수도 있다.

    프롬프트 명령창에서 adb reverse tcp:12345 tcp:12345  

    참고로 adb를 설치하려면 android sdk manager 에서 Andoid SDK Platform-tools 를 설치해야 생긴다.

    안드로이드 스튜디오의 경우 디폴트로 설치된다. 설치 경로를 잘 찾아 보시라.

    내 피씨에서는 Android SDK Platform-tools은 기본 디폴트 설치시 

    windows 10환경에서는 아래의 경로에 설치되었음

 

    C:\Users\Administrator\AppData\Local\Android\sdk\platform-tools

 


댓글을 달아 주세요

  1. Gowoo 2019.01.26 10:03

    큰 도움이 되었습니다 감사합니다 (__)