[Android] PHP로 구현하는 C2DM (안드로이드 푸쉬 서비스)

Posted by 빵빵빵
2012/02/16 18:35 전산(컴퓨터)/안드로이드



출처 : http://credenda.blog.me/80153084344

안드로이드에서도 푸쉬 서비스를 이용할 수 있다.

그러나 아직 베타서비스임을 명심할 것.

안드로이드에서 푸쉬 서비스를 이용하기 위해서는 SDK 2.2(Level 8, Froyo)이상으로 제작하여야

한다.


우선 푸쉬 서비스를 이용하기 위해서는 구글에 초대 요청을 해야 한다.

요청은 아래 사이트에서 가능하다.

http://code.google.com/intl/ko-KR/android/c2dm/signup.html


가 보면 안드로이드 C2DM을 위한 가입 절차라고 되어 있고,

하단에 동의할 것이냐는 체크박스가 나온다.

체크하고 Continue를 누르자


그럼 아래와 같은 양식이 나온다.

이 양식을 다 채우면 초대를 요청할 수 있다.

사용자 삽입 이미지

Package name of your Android app 이 것은 자신이 제작중인 앱의 패키지명이다.

Is your app published in Android Market? 이 것은 마켓에 등록된 앱인지 묻는 것이다.

Estimated total number of messages per day? 이 것은 하루에 어느정도의 메세지를 사용하는지를 묻는 것이다.

Estimated peak queries per second (QPS) 이 것은 초당 전송하는 쿼리의 양을 묻는 것이다.

(이 부분은 자세히 알 수 없다. 바로 위 항목과 겹치는 것 같은데 QPS를 검색해보기 바란다)

Additional information 이 것은 당신의 앱에 대해 추가로 말하고 싶은 것을 적으면 된다.

Contact email 이 것은 연락받을 수 있는 이메일 주소를 묻는 것이다.

Role(sender) account email 이 것은 당신이 (마켓등에서)사용할 지메일 주소를 적으면 된다.

Escalation contact information 이 것은 C2DM을 사용중 앱에 대해 문제(사건)가 발생시 급하게 연락할 수 있는 연락처를 적으면 된다. C2DM이 제한된다던지 하는 내용으로 추측된다.


이렇게 보내고 나면 다음과 같은 메일이 온다. 이 메일을 받으면 C2DM을 사용할 수 있다.

 

사용자 삽입 이미지



C2DM 문서를 보면 필요한 요소가 다음과 같다.

(원문 출처 : http://code.google.com/intl/ko-KR/android/c2dm/)

Components
Mobile DeviceThe device that is running an Android application that uses C2DM. This must be a 2.2 Android device that has Market installed, and it must have at least one logged in Google account.
Third-Party Application ServerAn application server that developers set up as part of implementing C2DM in their applications. The third-party application server sends data to an Android application on the device via the C2DM server.
C2DM ServersThe Google servers involved in taking messages from the third-party application server and sending them to the device.
Credentials
Sender IDAn email account associated with the application's developer. The sender ID is used in the registration process to identify a Android application that is permitted to send messages to the device. This ID is typically role-based rather than being a personal account—- for example, my-app@gmail.com.
Application IDThe application that is registering to receive messages. The application is identified by the package name from the manifest. This ensures that the messages are targeted to the correct application.
Registration IDAn ID issued by the C2DM servers to the Android application that allows it to receive messages. Once the application has the registration ID, it sends it to the third-party application server, which uses it to identify each device that has registered to receive messages for a given application. In other words, a registration ID is tied to a particular application running on a particular device.
Google User AccountFor C2DM to work, the mobile device must include at least one logged in Google account.
Sender Auth TokenA ClientLogin Auth token that is saved on the third-party application server that gives the application server authorized access to Google services. The token is included in the header of POST requests that send messages. For more discussion of ClientLogin Auth tokens, see ClientLogin for Installed Applications.


휴대용 기기안드로이드 2.2 버전 이상에서 작동한다고 되어 있다.

앱용 서버는 앱과 통신하여 필요한 데이터를 받고, 필요할 때에 C2DM 서버에 전송하여 해당 기기에 필요한 데이터를 전송하게 된다. 앱용 서버를 거치지 않고 기기에서 C2DM 서버로 바로 전송할 수도 있다. 이 경우 목적 기기까지 수초 내로 메세지가 전달된다.

C2DM 서버는 말 그대로 기기로 메세지를 전송한다.

그리고 C2DM 서버에 요청하고 최종적으로 기기에 메세지를 전달하기 위해 필요한 것들은 다음과 같다.

Sender ID 이 것은 신청 당시 Role(sender) account email에 적은 이메일 주소와 같다.

Application ID는 앱의 패키지명이다. (본인이 틀릴 수 있음)

Registration ID는 Application ID와 Sender ID를 C2DM 서버에서 보내서 얻는 ID다 (추후 설명)

Google User Account 이는 기기에 적어도 하나 이상의 구글 계정이 등록되어 있어야 함을 의미한다. 안드로이드 기기로 마켓을 이용하기 위해 사용자가 필수적으로 입력하는 사항이니, 마켓에서 앱을 받는 사용자라면 앱에서 메세지를 충분히 받을 수 있다.

Sender Auth Token 이 토큰은 구글 서비스에 접근하기 위해 필요하며, 앱 서버에서 생성하여 사용한다.


우선 C2DM을 사용하기 위해 권한을 획득하자.

패키지 내의 Manifest를 열고 다음의 코드를 삽입한다.

(다음의 코드는 Android Cloud to Device Messaging Framework 문서에서 발췌하였다.)

(원문 출처 : http://code.google.com/intl/ko-KR/android/c2dm/)

<!-- Only this application can receive the messages and registration result -->

<!-- 앱이 C2DM을 사용하기 위해 등록하고 그 결과를 받을 수 있는 권한을 획득한다.-->

<!-- 주황색의 패키지명은 <manifest package="">에 들어있는 값으로 대체한다. -->

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

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


<!-- This app has permission to register and receive message -->

<!-- 앱이 메시지를 등록하고 받을 수 있는 권한을 획득한다.-->

<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />


<!-- Send the registration id to the server -->

<!-- C2DM을 이용하려면 기기가 네트워크를 사용해야 하므로 인터넷 권한을 획득한다.-->

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

그리고 앱에서 Registration ID를 발급받아야 한다.

이는 앱이 켜지는 순간 실행될 수 있도록 적절히 작성하면 된다. 다음의 코드를 보자.이는 앱이 켜지는 순간 실행될 수 있도록 적절히 작성하면 된다. 다음의 코드를 보자.

참고로, 자주색은 없어도 되거나 개발자에 의해 수정되어야 하는 것들이다. 주석을 참고하자.

(원문 출처 : http://code.google.com/intl/ko-KR/android/c2dm/)

// 선택사항이지만, 본인은 Registration ID를 저장해 놓고, 없으면 발급받고 있으면 건너뛰는 방식으로 작성하였다. 매번 발급받아도 되지만 한 번만 발급 받는 것이 여러모로 효율적이다.

SharedPreferences shrdPref = PreferenceManager.getDefaultSharedPreferences(this);

String registrationID = shrdPref.getString("registrationID", null);

shrdPref = null;

if (registrationID == null) {

    // Request registration ID for C2DM

    // 아래는 Application ID와 Sender ID로 Registration ID를 발급받기 위해 인텐트를 생성하는 것이다.

    Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");

    // 아래는 Application ID를 넘기는 것으로, "app"이라는 키로 Parcelable값을 넘긴다.

    registrationIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0)); // App ID

    // 아래는 Sender ID를 넘기는 것으로, "sender"라는 키로 개발자의 구글 ID를 넘긴다.

    registrationIntent.putExtra("sender", "ID@gmail.com"); // Developer Gmail ID

    // 서비가 시작되면 등록 절차를 밟는다.

    startService(registrationIntent); // Try request registration

}

이제 받아온 Registration ID를 사용하여 메세지를 등록하고, 받은 메세지를 처리하는 코드를 짜보자.

패키지 어딘가에 클래스를 만들어보자.

본인은 C2DMReceiver.java로 만들었다. 클래스는 BroadcastReceiver를 상속받는다.

(원문 출처 : http://code.google.com/intl/ko-KR/android/c2dm/)

public class C2DMReceiver extends BroadcastReceiver {

    // 아래 메서드는 리시버가 뭔가를 받았을 때 실행된다.

    @Override

    public void onReceive(Context context, Intent intent) {

        if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION"))

        {

            // 만약 받은 인텐트가 등록절차를 밟은 것이라면 아래 메서드를 실행한다.

            getRegistrationID(context, intent);

        }

        else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE"))

        {

            // 만약 받은 인텐트가 메세지라면 아래 메서드를 실행한다.

            getMessage(intent, context);

        }

    }

    // 등록절차가 마무리되면 아래 메서드가 실행된다.

    public void getRegistrationID(Context context, Intent intent) {

        // registration_id라는 키로 Extra가 들어있다. 이 것을 취하자.

        String id = intent.getStringExtra("registration_id");

        // 만약 error Extra가 있다면 에러 처리를 한다.

        if (intent.getStringExtra("error") != null) {

            // Registration failed. Try again later, with backoff.

            Log.e("BestClinic", intent.getStringExtra("error"));

            return;

        // 만약 ID가 제대로 넘어왔으면 아래 명령들을 실행한다.

        } else if (id != null) {

            // Send the registration ID to the 3rd party site that is sending the messages.

            // This should be done in a separate thread.

            // When done, remember that all registration is done.

            // 위에서 설명했 듯 등록 절차는 한 번만 밟을 것이므로 Registration ID를 저장한다.

            SharedPreferences shrdPref = PreferenceManager.getDefaultSharedPreferences(context);

            SharedPreferences.Editor editor = shrdPref.edit();

            editor.putString("registrationID", id);

            editor.commit();

        }

    }

}

에러 메세지는 다음과 간다.

(원문 출처 : http://code.google.com/intl/ko-KR/android/c2dm/)

Error CodeDescription
SERVICE_NOT_AVAILABLEThe device can't read the response, or there was a 500/503 from the server that can be retried later. The application should use exponential back off and retry.
ACCOUNT_MISSINGThere is no Google account on the phone. The application should ask the user to open the account manager and add a Google account. Fix on the device side.
AUTHENTICATION_FAILEDBad password. The application should ask the user to enter his/her password, and let user retry manually later. Fix on the device side.
TOO_MANY_REGISTRATIONSThe user has too many applications registered. The application should tell the user to uninstall some other applications, let user retry manually. Fix on the device side.
INVALID_SENDERThe sender account is not recognized.
PHONE_REGISTRATION_ERROR

Incorrect phone registration with Google. This phone doesn't currently support C2DM.


위의 리시버가 작동하기 위해서는 Manifest에 리시버가 등록되어 있어야 한다.

패키지 Manifest을 열고 다음의 코드를 삽입하자.

<!-- 자신이 생성한 리시버의 이름을 기입한다. 패키지 명은 알맞게 변경할 것-->

<receiver android:name=".utility.C2DMReceiver" android:permission="com.google.android.c2dm.permission.SEND">

    <!-- Receive the actual message -->

    <!-- 실제 메세지를 받아주는 역할을 부여한다 -->

    <intent-filter>

        <action android:name="com.google.android.c2dm.intent.RECEIVE" />

        <category android:name="com.android.c2dm" />

    </intent-filter>

    <!-- Receive the registration id -->

    <!-- 등록된 Registration ID를 받는 역할을 부여한다 -->

    <intent-filter>

        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

        <category android:name="com.android.c2dm" />

    </intent-filter>

</receiver>


이제 Registration ID를 받았으니 앱 서버에 전송해보자.

전송은 POST를 이용하겠다.

아래 코드는 자신이 원하는 곳에 입력하면 된다.

가령 질문을 하거나 메세지를 보내는 경우 보내기 버튼을 누르면 작동하도록 말이다.

// HTTP 통신을 하기 위해 두 변수를 선언한다.

URL url;

HttpURLConnection con;

// 받아온 Registration ID를 불러온다.

SharedPreferences shrdPref = PreferenceManager.getDefaultSharedPreferences(this);

String registrationID = shrdPref.getString("registrationID", null);

shrdPref = null;

// 서버에 전송한 후 피드백을 받을 일이 있다면 아래 변수를 선언하자.

String receivedMessage;

try {

    // 통신할 서버 주소를 적는다. 본 포스트에서는 PHP를 사용하니 php 경로를 적는다.

    url = new URL("http://domain.co.kr/php.php");

    // 주소로 접속하여 접속 객체를 얻는다.

    con = (HttpURLConnection)url.openConnection();

    // 아래는 POST 통신을 하기 위한 설정이다.

    con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

    con.setDoOutput(true);

    con.setDoInput(true);

    con.setRequestMethod("POST");

    // 아래는 POST 통신으로 서버에 전달할 값들을 나열하는 것이다.

    // 우선 Registration ID를 넣자.

    String postBody = "registrationID=" + registrationID;

    // 그리고 추가적으로 서버에 전달할 값이 있다면 &와 함께 Key-Value로 넣는다.

    postBody += "&message=" + message;

    // 위와 같이 하면 body는 registrationID=??????????&message=???? 식이 될 것이다.

   

    // 이제 서버로 전송하자.

    OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());

    wr.write(postBody);

    // 전송이 완료되면 반드시 출력 접속을 끊는다.

    wr.flush();

   

    // 만약 서버 PHP에서 피드백을 준다면 아래 소스가 필요할 것이다.

    BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8"));

    String line = null;

    while ((line = rd.readLine()) != null) {

        receivedMessage += line;

    }

    line = null;

    rd.close();

    wr.close();

   

    // 아래는 본인이 피드백 처리를 한 것이므로 무시해도 상관 없다.

    if (receivedMessage.equals("complete")) {

        handler.sendEmptyMessage(1);

    } else {

        handler.sendEmptyMessage(2);

    }

} catch(UnknownHostException e){

    e.printStackTrace();

} catch (Exception e) {

    e.printStackTrace();

}

본인은 우선 서버에 Registration ID가 당도하면 이를 DB에 저장한다.

주의할 것은, 아이폰과 달리 안드로이드의 Registration ID는 유일하지 않다.

발급받을 때마다 ID가 변한다.

해서 본인은 다른 고유값을 같이 넘겨주어 ID가 바뀌어도 같은 사용자임을 식별한다.

여튼 작은 팁이었고, 본인은 질문을 하고 앱 서버에서 답변이 입력되면

C2DM으로 쏴주는 형식으로 작성하였으므로

앱 서버에서 답변이 입력되었을 때 어떻게 C2DM을 쏘는지 보겠다.

아래 코드는 PHP로 작성되었다.

<?php

    // 아래는 기본 폼이지만 몇가지 개발자에게 맞춰야 하는 것이 있다.

    $accountType = "HOSTED_OR_GOOGLE";

    // 앱에서 Registration ID를 얻기 위해 기입했던 개발자의 구글 계정

    $Email = "ID@gmail.com";

    // 그 계정의 비밀번호

    $Passwd = "password";

    $source = "test-1.0";

    $service = "ac2dm";

    // 앱에서 서버로 전송할 때 사용했던 Registration ID

    $registration_id = RegistrationIDFromDB();

   

    $ch = curl_init();  

    // 아래 부분은 Sender Auth Token을 얻는 부분이다.

    curl_setopt($ch, CURLOPT_URL, "https://www.google.com/accounts/ClientLogin");

    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);

    curl_setopt($ch, CURLOPT_POST, true);

    curl_setopt($ch, CURLOPT_HEADER, true);

    curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);

    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

   

    $account_info = array("accountType" => $accountType, "Email" => $Email, "Passwd" => $Passwd, "source" => $source, "service" => $service);

   

    curl_setopt($ch, CURLOPT_POSTFIELDS, $account_info);

   

    $response = curl_exec($ch);

    $auth_tmp = substr(strstr($response, "Auth="), 5);

    $auth = substr($auth_tmp, 0, strlen($auth_tmp)-1);

    // collapse_key 는 메세지를 식별하기 위해 쓰이는 것 같으나 정확한 것은 아직 알지 못했다.

    $collapse_key = 2;

    // 사용자 기기로 전달할 메세지와 기타 데이터를 나열한다. registration_id와 collapse_key는 필수이나 data에 해당하는 부분은 선택사항이다.

    $data = "registration_id=".$registration_id."&collapse_key=".$collapse_key."&data.answerer=$answerer&data.no=$no";

    // 만약 data로 한글을 전달하고 싶은데 막상 단말에서 받았을 때 값이 없거나 알 수 없는 문자가 입력된다면 다음과 같이 해보라.

    // "&data.answerer='관리자'"

    // "&data.answerer=".urlencode("관리자")."

    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

   

    $headers = array(

        "Content-Type: application/x-www-form-urlencoded",

        "Content-Length: ".strlen($data),

        "Authorization: GoogleLogin auth=".$auth

    );

   

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, "https://android.apis.google.com/c2dm/send");

    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

    curl_setopt($ch, CURLOPT_POST, true);

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

    $result = curl_exec($ch);

    curl_close($ch);

?>

이제 C2DM 서버에서 사용자에게 메세지를 전송할 것이다. 이를 어떻게 처리하는지 살펴보자.

위에서 작성한 리시버 클래스를 다시 열어보자.

else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE"))

{

    getMessage(intent, context);

}

이 부분이 생각나는가? 여기서 명시했던 getMessage()메서드를 작성해보자.

본인은 메세지가 도착하면 상태바에 알림을 띄우는 것으로 작성하였다.

 private void getMessage(Intent intent, Context context) {

    /* 이하 내용은 일종의 팁이다. 본 메서드는 개발자 입맛대로 작성하시면 된다. */

    /* 모두 자주색으로 처리해야 옳으나 가독성이 안좋기에 제한다. */

    /* 대신 필요한 곳만 고치면 본 팁을 활용할 수 있도록 해당 부분을 파란색 처리하겠다. */

    // 알림을 띄우기 위해서 서비스를 불러온다.

    NotificationManager notificationManager = (NotificationManager)context.getSystemService(Activity.NOTIFICATION_SERVICE);

    // 서버에서 data.key 형식으로 보낸 data는 모두 intent.getExtras에 들어있다.

    String text = intent.getExtras().getString("answerer") + "님께서 답변하셨어요!";

    // 알람이 오면 어떤 방식으로 알릴지 앱에 설정된 정보를 불러온다.

    SharedPreferences shrdPref = PreferenceManager.getDefaultSharedPreferences(context);

    // 상태바에 알림을 표시할 건지

    boolean isNoti = bestClinicPref.getBoolean("noti", true);

    // 소리로 알릴건지

    boolean isSound = bestClinicPref.getBoolean("sound", true);

    // 진동으로 알릴건지

    boolean isVib = bestClinicPref.getBoolean("vib", true);

    // Set the icon, scrolling text and timestamp

    // 상태바에 띄울 알림을 생성한다. drawable은 상태바에 쓰일 아이콘,

    // text는 알림이 도착한 순간 상태바 전체에 나타나는 메세지,

    // 시간은 상태바에서 메세지를 받은 시간이다.

    Notification notification = new Notification(R.drawable.crown, text,

            System.currentTimeMillis());

    // 아래는 위에서 불러온 방식으로 알림 방식 설정을 하는 것이다.

    if (isSound && isVib) {

      notification.defaults = Notification.DEFAULT_SOUND|Notification.DEFAULT_VIBRATE;

    } else if (isSound && !isVib) {

      notification.defaults = Notification.DEFAULT_SOUND;

    } else if (!isSound && isVib) {

      notification.defaults = Notification.DEFAULT_VIBRATE;

    }

   

    // 아래 코드는 사용자가 알림을 터치했을 때 액티비티를 호출하기 위한 인텐트이다.

    PendingIntent contentIntent = PendingIntent.getActivity(context, 0,

            new Intent(context, MyApp.class)

                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

                    .putExtra("no", Integer.parseInt(intent.getExtras().getString("no")))

                    .putExtra("answerer", intent.getExtras().getString("answerer")),

            PendingIntent.FLAG_UPDATE_CURRENT);

    // Set the info for the views that show in the notification panel.

    // 상태바를 내렸을 때 알림 패널에 대한 설정이다.

    notification.setLatestEventInfo(context, "제목", "내용", contentIntent);


    // Send the notification.

    // We use a layout id because it is a unique number.  We use it later to cancel.

    // 알림을 발효한다.

    notificationManager.notify(R.layout.content_layout, notification);

}

이로서 C2DM의 준비와 메세지 등록, 그리고 메세지 수신까지 완료하였다.

어떻게 보면 아이폰에 비해 다소 복잡할 수도 있겠다. 하지만 아이폰과 달리 알림이 도착했을 때

다양한 행동을 취할 수 있다는 점에서 창의적으로 서비스 제공자와 소비자간의 상호작용을 보여줄 수 있을거라 생각한다.


지금까지 PHP로 구현하는 C2DM이었다.


Q&A : lifecluee@gmail.com


출처 : http://blog.naver.com/PostView.nhn?blogId=legendx&logNo=40140952521&viewDate=&currentPage=1&listtype=0&userTopListOpen=false&userTopListCount=5&userTopListManageOpen=false&userTopListCurrentPage=undefined

2012/02/16 18:35 2012/02/16 18:35

이 글에는 트랙백을 보낼 수 없습니다