IT_Games/Unity3D

Unity3D Android GCM Integration

JJun ™ 2013. 8. 6. 01:53


 출처:  http://westwoodforever.blogspot.kr/2013/06/unity3d-android-gcm-integration-1.html


 

 

 

Unity3D Android GCM Integration

- 1. 준비작업

 

 

 구글의 푸시 기능을 사용하기 위해 구글 API 콘솔에 API 프로젝트를 만들어 Key 생성까지
해봤습니다. 이번에는 유니티3D 안드로이드 GCM(Google Cloud Messaging) 연동을 정리해보겠습니다.


1. GCM 프로젝트 생성
 구글 API 콘솔에서 프로젝트를 만들고 Key를 생성합니다.


2. 안드로이드 플러그인 프로젝트 생성

 

 유니티3D 안드로이드 플러그인 Jar 프로젝트를 생성합니다.

저는 com.test.gcm 이라고 만들었습니다. GCM은 안드로이드 2.2부터 지원하므로 최소 요구 SDK를

API 8로 설정해줍니다.


3. Google Cloud Messaging for Android Library 설치 및 임포트

 

 

안드로이드 SDK 메니져를 열어서 Extras에 있는 Google Cloud Messaging for Android Library를

인스톨합니다.

 

 

GCM은 SDK디렉터리/extras/google/gcm/ 에 설치됩니다.

먼저 클라이언트부터 할 것이니 gcm-client/dist에 있는 gcm.jar 파일을 Java Build Path의 Add External JARs로 추가합니다.


4. AndroidManifest 수정 및 Jar파일 복사

 

     <!-- GCM 퍼미션 -->

    <permission android:name="com.test.gcm.permission.C2D_MESSAGE" android:protectionLevel="signature" />

 

    <uses-permission android:name="com.test.gcm.permission.C2D_MESSAGE" />
   

 

    <!-- GCM 리시버 퍼미션 -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
   

    <!-- 구글 계정 퍼미션 -->

    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>

 

    <!-- 메시지 수신시 wake up을 위한 퍼미션 -->
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
   
    <!-- 인터넷 접속 퍼미션 -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- 진동을 위한 퍼미션 -->
    <uses-permission android:name="android.permission.VIBRATE"/>


 <application>
        ...

        <!-- GCM 리시버 -->
        <receiver
            android:name="com.google.android.gcm.GCMBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
                <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
                <category android:name="com.test.gcm"/>
            </intent-filter>
        </receiver>

 

        <!-- GCM 리시버에서 돌리는 서비스 -->
        <service android:name=".GCMIntentService"></service>
...
</application>

 

 

   


유니티3D Plugins/Android에 복사한 AndroidManifest.xml을 위와같은 것들을 추가해줍니다.

붉은색 부분은 각자의 패키지명으로 해줍니다.

 

마지막으로 gcm-client/dist에 있는 gcm.jar 파일을 유니티 안드로이드 플러그인 디렉터리에

복사해줍니다.

이것으로 유니티3D 안드로이드 GCM 연동 준비작업이 끝났습니다.

다음에는 GCM 클라이언트 처리 부분을 정리하겠습니다.

 

 

 


 

 

 

 

 

Unity3D Android GCM Integration

- 2. Sender ID 등록 및 Push 메세지 핸들링 후 알림 띄우기

유니티3D 안드로이드 푸시 처리를 위한 GCM 연동 준비작업을 정리했었습니다. 
이번에는 안드로이드 디바이스에서 GCM 푸시를 받을 수 있게 Sender ID를 등록하고 푸시 메세지 핸들링을 처리할 GCMBaseIntentService를 상속받는 GCMIntentService 클래스 클라이언트쪽 작업을 정리해보겠습니다.
  • 자체 서버(JSP든 PHP든) + DB + 운영툴(공지 메세지 보내기)

보통은 GCM 푸시하려면 위와 같은 구조로 서버 구성을 하겠죠.
제가 정리할 내용은 아래와 같은 구조입니다.

  • 자체 서버 없음. 앱 하나에서 GCM Push Send까지 다 함.
  • DB 없음. 등록된 디바이스는 오직 한개라고 정의하고 간단히 멤버변수에 때려박음.
  • 운영툴 없음. GUI TextFiled로 입력받은 스트링을 GCM에 보냄.


한마디로 with out Server 구조입니다. 즉, Client -> GCM -> 동일 Client 이런식이 되는거죠.

혼자 북치고 장구치고 다 하는 튜토리얼입니다.

 

일반적으론 각자 구현한 Server -> GCM -> Clients 이렇게 되겠죠.

제가 서버 개발자가 아니라서 일단 이렇게 하는데요, 서버와의 연동부분이 필요한 부분은 소스 구현없이

따로 살짝 언급하고 진행하도록 하겠습니다.


1. 안드로이드

 
public class MainActivity extends UnityPlayerActivity 
{
         private final String LOG_TAG = "UNITY_GCM_TEST";
       private final String GCM_SENDER_ID = "1111111111111";
       private String strRegId = null;
       @Override
       protected void onCreate(Bundle savedInstanceState) {
               super.onCreate(savedInstanceState);
               GCMRegistrar.checkDevice(this);
               GCMRegistrar.checkManifest(this);
 
               this
.strRegId = GCMRegistrar.getRegistrationId(this);
               if (this.strRegId == null || this.strRegId.equals("")) {
                       GCMRegistrar.register(this, GCM_SENDER_ID);
                       Log.d(LOG_TAG, "Sender Id Regist");
               } else {
                       Log.d(LOG_TAG, "Registered Sender Id " + this.strRegId);
               }
      }

 

}

 


앱이 시작되면 구글 GCM 서버에 Sender ID가 등록 안 되어 있다면 등록해서 디바이스의 고유한 등록 ID를 받아야 합니다. 이때 넘겨주는 Sender ID는 구글 API Project 사이트에서 등록했던 Project Number 값을 기입합니다.

결과로 받는 RegID는 각 디바이스별 고유 ID값입니다. 서버에서는 이 아이디를 참고해서 푸시를 보내는 것이죠. 현재 샘플에서는 자기 자신에게 보낼 것이기에 직접 멤버 변수로 RegID 를 가지고 있습니다.

서버에서는 DB에 저장을 해야겠죠. 참고로 앱을 삭제 후 다시 설치해 등록을 다시해도 RegID는 고유합니다.

이제 연동 준비작업에서 AndroidManifest.xml에 추가했던 GCMIntentService 클래스를 보겠습니다.

 

AndroidManifest.xml에만 추가되었기 때문에 실제로 클래스를 위와같이 하나 만들어줍니다.

Superclass는 com.google.android.gcm.GCMBaseIntentService입니다. 그리고 아래와 같이 작업합니다.

 

 
public class GCMIntentService extends GCMBaseIntentService 
{
        private final String LOG_TAG = "UNITY_GCM_TEST";
      private final static String GCM_SENDER_ID = "111111111111";
      public GCMIntentService() {
           super(GCM_SENDER_ID);
      }

      @Override
      protected void onError(Context arg0, String arg1) {
           // TODO Auto-generated method stub
           Log.d(LOG_TAG, onError : " + arg1);
      }
 
      @Override
      protected void onMessage(Context arg0, Intent arg1) {
           // TODO Auto-generated method stub
          String strTitle = arg1.getStringExtra("Title");
          String strMsg = arg1.getStringExtra("Msg");
          NotificationManager notificationManager = (NotificationManager)getSystemService
                                                                                                (
NOTIFICATION_SERVICE);
          Intent notificationIntent = new Intent(arg0, MainActivity.class);

 

          notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
                                                  | Intent.FLAG_ACTIVITY_SINGLE_TOP);

          PendingIntent pendingIntent = PendingIntent.getActivity(arg0, 0 , notificationIntent, 0);
          Notification notification = new Notification();
          Resources res = getResources();
          notification.icon = res.getIdentifier("com.test.gcm:drawable/app_icon", null, null);
          notification.defaults = Notification.DEFAULT_ALL;
          //notification.vibrate = new long[]{100, 50, 100, 50};
          notification.flags = Notification.FLAG_AUTO_CANCEL;
          notification.tickerText = "Ticker Text Test.";
          notification.setLatestEventInfo(arg0, strTitle, strMsg, pendingIntent);
          notificationManager.notify(0, notification);
     }
     @Override
     protected void onRegistered(Context arg0, String arg1) {
           // TODO Auto-generated method stub
           Log.d(LOG_TAG, onRegistered : " + arg1);
          // TODO : 서버가 있다면 RegID를 전송해서 DB에 등록한다.
     }
     @Override
     protected void onUnregistered(Context arg0, String arg1) {
           // TODO Auto-generated method stub
           Log.d(LOG_TAG, onUnregistered : " + arg1);
          // TODO : 역시나 서버가 있다면 삭제요청을 보낸다.
     }
}

 


 
GCM으로부터 푸시를 받으면 알림을 띄우고 알림을 클릭하면 해당 앱(Activity)을 실행하는 부분입니다.

붉은색 Sender ID는 마찬가지로 Project Number입니다. 중요한 함수는 onRegistered와 on Message

입니다.

onRegistered의 경우 현재는 서버 연동 없이 진행하는 튜토리얼이라 RegID 로그만 출력하고 있지만

저 부분에서 각자의 서버와 연동을 해줘야합니다. 즉, 구현한 서버에 디바이스의 고유 ID를 보내주고

서버에서 DB로 관리를 해야하는 것이죠.

두번째로 onMessage에서는 GCM서버에서 날라온 푸시를 Notification 처리하는 부분입니다.

이 부분을 처리하면서 간단하게는 진동 이슈가 있었고 R.java와의 연동 이슈문제알림을 클릭해

이미 실행중인 Activity를 활성화 하는 부분의 이슈가 있었습니다. R.java때문에 생긴 R drawable 이슈는

지금처럼 유니티 엔진베이스가 아닌 순수 안드로이드 게임이나 어플이라면 생기지 않을 이슈죠.


2. 유니티3D

 
using UnityEngine;
 
public class GCMManager : MonoBehaviour 
{
        static GCMManager _instance;
        private AndroidJavaObject curActivity;
        public static GCMManager GetInstance()
        {
                 if ( _instance == null ) {
                          _instance = new GameObject("GCMManager").AddComponent<GCMManager>();
                 }
                 return _instance;
        }
        void Awake()
        {
                 AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
                 curActivity = jc.GetStatic<AndroidJavaObject>("currentActivity");
        }
}

 


 

간단한 GCMManager 싱글톤 컴포넌트입니다.

아직은 유니티3D에서 위와 같이 자바와 연결 작업만하고 추가적인 작업은 없습니다.

 
  using UnityEngine;

 public class TestGUI : MonoBehaviour 
 {
        void Start()
        {
        }
        // Update is called once per frame
        void Update () 
        {
                 if (Application.platform == RuntimePlatform.Android)
              {
                        if (Input.GetKey(KeyCode.Escape))
                    {
                        Application.Quit();
                        return;
                    }
                 }
        }
        void onGUI()
        {
                 float fYpos = 0;
                 GUI.Label(new Rect(0, fYpos, 400, 100), "Unity3D Android GCM Test");
         }

 

}

 


 

TestGUI 컴포넌트도 마찬가지입니다. GUI Label하나 출력하는 것말고는 없죠.

GCM 푸시를 받아서 알림을 띄우는 처리까지 정리해봤습니다.

 

다음에는 언급한데로 실제 서버는 아니지만 안드로이드 자바에서 GCM에 메세지를 Send 처리하는 것을
정리해보겠습니다.

 

 

 


 

 

 

 

 

Unity3D Android GCM Integration

- 3. Send Single, Multi Push(without Server)

 

 

유니티3D에 안드로이드 GCM 클라이언트 부분인 Sender ID 등록과 푸시 메세지 핸들링 후 알림 띄우기까지 정리해봤습니다. 이번에는 마지막으로 서버측 처리를 정리하겠습니다. 하지만 미리 언급했듯이 JSP나 PHP등의 서버가 아닌 안드로이드 자바딴에서 클라이언트 + 서버 기능을 사용하는 내용입니다.


1. GCM Server 라이브러리 준비

 

 

안드로이드 Jar 프로젝트 빌드를 위해 gcm 서버 기능을 사용하기 위해 gcm-server.jar 파일을 자비 빌드 패스에 추가해줍니다. 위치는 스샷에 보이는 것과 같이 androidsdk/extras/google/gcm/gcm-server/dist 에 있습니다.

 

 

 

 

당연히 유니티3D 최종 빌드를 위해 플러그인 디렉터리에도 복사를 해줍니다.

 

 

마지막으로 androidsdk/extras/google/gcm/gcm-server/lib 에 있는 json_simple-1.1.jar파일을 유니티3D 플러그인 디렉터리에 복사해줍니다.

 

이것이 없다면 java.lang.NoClassDefFoundError: org.json.simple.JSONValue 오류가

발생합니다.




2. GCM에 단일 또는 멀티로 푸시하기

원래는 자체 서버딴에서 처리해야할 푸시 기능을 안드로이드 프로젝트에 자바로 구현한 부분을
정리해보겠습니다. 아래는 기존 MainActivity에 추가 된 것입니다.

 
public class MainActivity extends UnityPlayerActivity  
{
      private final String LOG_TAG = "UNITY_GCM_TEST";
      private final String GCM_SENDER_ID = "xxxxxxxxxxxx";

 

      // API ACCESS KEY
      private final String API_KEY = "xxxxxxxxxxxxxxx";

       

      // 이 앱을 실행중인 디바이스의 RegId
      private String strRegId = null;

       

      // 현 디바이스와 다른 디바이스 모두를 가지고 있는 RegId List
      private List<String> regIdList = null;
      // 메세지 전송 실패시 재시도 횟수
      private final int MSG_SEND_RETRY = 3;
      ...

 

      private
class GCMPushTask extends AsyncTask<Message, Void, Void> 
      {

            @Override
             protected Void doInBackground(Message... params) {
                    // TODO Auto-generated method stub
                    Sender gcmSender = new Sender(API_KEY);
                    try {
                           // 자신에게만 푸시 요청
                           if (regIdList == null)
                           {
                                  Result gcmResult = gcmSender.send(params[0], strRegId,

                                                                  MSG_SEND_RETRY);

                                  if (gcmResult.getMessageId() != null) {
                                         Log.d(LOG_TAG, "Push Success");
                                  } else {
                                         Log.d(LOG_TAG, "Push Failed " + gcmResult.getErrorCodeName());
                                  }
                          
                           else
                           {
                                  // 등록된 다른 디바이스까지 요청
                                  MulticastResult gcmMultiResult = gcmSender.send(params[0], regIdList
                                                                                    MSG_SEND_RETRY);
                                  gcmMultiResult.getTotal();
                                  gcmMultiResult.getSuccess();
                           }
                    } catch (IOException e) {
                           // TODO Auto-generated catch block
                           e.printStackTrace();
                    }
                    
                    return
null;
             }
      }
      public void PushMsg_U(final String strMsg) {
             runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                           // TODO Auto-generated method stub
                           Message.Builder msgBuilder = new Message.Builder();
                           // 디바이스가 활성화 상태일 때 보여주려면 true
                           msgBuilder.delayWhileIdle(true);
                           // 디바이스 비활성화일 때 보관되는 시간.
                           msgBuilder.timeToLive(3);
                           msgBuilder.addData("Title", "Unity3DTestGCM");
                           msgBuilder.addData("Msg", strMsg);
                           
                           GCMPushTask gcmPushTask =
new GCMPushTask();
                           gcmPushTask.execute(msgBuilder.build());
                    }
             });
      }
      public void AddOtherDevice_U() {
             // TODO : 실제로는 서버에서 정보를 얻어오거나 서버에 있어야할 것들
             if (this.regIdList == null) {
                    this.regIdList = new ArrayList<String>();
                    this.regIdList.add(this.strRegId);
                    this.regIdList.add("xxxxxxxxxxxxxxxxxxxxxxxxx");
             }
      }

 

 }

 


 

API_KEY 부분은 구글 API 콘솔 페이지에 있는 Api Access Key값을 기입해줍니다.

 

그런데 제가 처음 API Key생성 관련 정리할 때는 서버키를 생성했었는데 그냥 기존에 있는 Key for browser apps의 키를 사용해도 테스트가 되긴하네요. 최종 릴리즈때는 잘 모르겠지만요.
아무튼 서버에서 적용하신다면 서버키를 기입하시기 바랍니다.

Message등은 android.os.Message 가 아니라 com.google.android.gcm.server.Sender 에 있는 Message입니다. import를 잘해줘야 충돌이 없습니다. 
GCM 푸시할 메세지 빌드는 runOnUiThread(new Runnable() { 에서 처리하고 실제 Sender를 통해서 send하는 네트워크와 관련된 처리는 AsyncTask<Message, Void, Void> { 사용해야합니다.
그렇지 않다면 android.os.NetworkOnMainThreadException 에러가 발생합니다.

마지막에 List<String>에 add를 하는 스트링중 strRegId는 자신의 것이고 그 밑에 xxxxx쭉 있는것은
다른 디바이스에서 실행해서 로그로 확인한 RegID를 넣은 것입니다. 주석에도 있듯이 이 부분은 서버에서 처리되어야 할 부분이죠. 물론 앱에 모든 유저의 RegId값을 저런식으로 다 알고 있다면야 앱에서도 처리는 가능하겠습니다.


아래는 유니티3D 내용입니다.

 

 

 // GCMManager.cs

 public class GCMManager : MonoBehaviour 

 { 

       ...

       public string strGCMMsg = string.Empty;
       ...

      

       public
void PushMsg(string strMsg)
       {
           curActivity.Call("PushMsg_U", strMsg);

       }

       public void AddOtherDevice()
       {
           curActivity.Call("AddOtherDevice_U");
       }
 }
 // TestGUI.cs
 void onGUI()
 {
       ...
      fYpos += 50;
      GCMManager.GetInstance().strGCMMsg = GUI.TextField(new Rect(0, fYpos, 300, 50),          

                                                                       GCMManager.GetInstance().strGCMMsg);

      fYpos += 50;

      if (GUI.Button (new Rect(0, fYpos, 100, 50), "Push Msg") == true)
      {
            GCMManager.GetInstance().PushMsg( GCMManager.GetInstance().strGCMMsg );
      }
 }
 

 

  fYpos +=
50;
  if (GUI.Button (new Rect(0, fYpos, 100, 50), "Add Device") == true)
  {
       GCMManager.GetInstance().AddOtherDevice();
  }

 

 


 간단하게 푸시 메세지 버튼과 다른 디바이스의 정보를 추가하는 버튼, 푸시 메세지 내용을 입력할 텍스트 필드를 추가했습니다.

 

A500 태블릿의 테스트 화면입니다. 한글도 잘네요. 알림을 클릭하면 앱 실행도 잘됩니다.
앱이 종료된 상태든 백그라운드에 실행되는 상태든 상관없이 잘됩니다.   

 

갤럭시S2에도 앱을 설치해서 RegId를 추출한 후 위에 설명한 소스와 같이 소스에 넣은후에 다시 빌드해서 A500 태블릿에만 새로운 버젼을 적용했습니다. 메세지를 보내기 전 스샷입니다.    

 

오른쪽이 A500이고 왼쪽이 갤럭시S2입니다. 멀티 푸시도 잘되네요.     
자체 서버에서 DB와 연동같은 부분은 숙제로 남았네요.
하지만 이미 서버 개발자 이신분들은 위 소스를 참고해서 쉽게 개발할 수 있을 듯 싶네요.