2015年1月9日

【Android】接收GCM推播通知訊息

各位Android安卓開發者大家好,首先在與大家分享文章之前,小黑人在此先向大家拜個早年,祝大家新年快樂,事事順利!

哈,去年小黑人寫作文章的進度有比較緩慢一點,在此先跟大家說聲抱歉,但是我們都要向前看齊,去年已經過去了,新的一年要好好把握,所以小黑人決定今年的第一篇文章就獻給許多App軟體都必備的"推播"機制

首先,在分享推播文章之前,我們先了解"推播"到底是什麼?
推播其實就是Google提供的GCM伺服器(Google Cloud Messaging)用來讓後端與行動裝置進行訊息的發送與接收,所以我們的手機狀態列上常常會顯示例如最新消息或優惠訊息等資訊,那我們手機端要怎麼運用GCM來接收後台發送的推播訊息呢?就讓我們繼續看下去吧~


好,在我們開始撰寫推播的接收程式前,我們要先將GCMLibrary導入我們的專案中,這樣我們才可以運用GCM本身的內部推播機制,所以一開始會碰到的問題是GCMLibrary在哪呢?

答案就是在"Google Play Services"裡,大家不用上網找這個Library在哪裡,其實在你的電腦裡就可以找到,首先步驟是:開啟Android SDK Manager(就是Update SDK版本的地方),開啟後往下滑動到最底部會有一個Extras資料夾,展開Extras資料夾後裡面會有一個Google Play Services,看到這個項目就不用懷疑在左方打勾然後進行安裝就可以囉,接下來安裝下載完成後檔案會存在SDK的資料夾裡,我們再把檔案抓出來導入到我們的專案裡就完成囉。

Google Play ServicesLibrary位置 : 你的Android SDK路徑\sdk\extras\google\google_play_services\libproject裡的google-play-services_lib專案,將這個專案import到你的專案列表裡。


目前導入google-play-services_lib專案後,這個專案和您的開發專案還沒有任何關係,所以您要在您的專案裡引用google-play-services_lib專案,步驟是:在您的專案上按右鍵,點選Properties,開啟視窗後左側列表點選Android,在下方Library區域裡按Add,將google-play-services_lib加入就可以囉。

好,前置作業都完成後,接下來就要正式開始撰寫推播的接收程式囉!

1.
 首先,開啟AndroidManifest.xml進行撰寫,加入下方的權限設定Application內的資訊。(其實Google有提供GCM的實作範例,裡面範例都寫的很清楚,小黑人也是跟著範例實作)

權限部分加入 : 
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<permission android:name="
您的Package Name.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="
您的Package Name.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

Application
內加入 : 
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />

<receiver
android:name=".GcmBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND"
>
   
 <intent-filter>
       
 <action android:name="com.google.android.c2dm.intent.RECEIVE" />
       
 <category android:name="您的Package Name" />
   
 </intent-filter>
</receiver>
       
 
<service android:name=".GcmIntentService" />

放入這些程式碼,AndroidManifest的部分就完成囉!


2. 建立GcmBroadcastReceiver.java,這個class算是在接收GCM的呼叫,收到通知後啟動Service
public class GcmBroadcastReceiver extends WakefulBroadcastReceiver
{
   
 @Override
   
 public void onReceive(Context context, Intent intent) 
   
 {
       
 ComponentName comp = new ComponentName(context.getPackageName(), GcmIntentService.class.getName());
       
 startWakefulService(context, (intent.setComponent(comp)));
      
  setResultCode(Activity.RESULT_OK);
   
 
}
}


3. 建立GcmIntentService.java,這個class算是收到GCM的呼叫通知後,解析傳來的通知訊息
public class GcmIntentService extends IntentService
 
{
   
 public static final int NOTIFICATION_ID = 1;
   
 private NotificationManager mNotificationManager;
   
 NotificationCompat.Builder builder;
    public static final String TAG = "GCM Demo";

   
 public GcmIntentService() 
   
 {
       
 super("GcmIntentService");
   
 }

   
 @Override
   
 protected void onHandleIntent(Intent intent)
   
 {
       
 //解析收到的文字訊息再傳入sendNotification進行顯示
       
 Bundle extras = intent.getExtras();
       
 GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
       
 String messageType = gcm.getMessageType(intent);

       
 if (!extras.isEmpty()) 
       
  
           
 if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) 
           
 {
               
 sendNotification("Send error: " + extras.toString());
           
 } 
           
 else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) 
           
 {
               
 sendNotification("Deleted messages on server: " + extras.toString());
           
 }
           
 else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) 
           
 {
               
 for (int i = 0; i < 5; i++)
               
 {
                   
 Log.i(TAG, "Working... " + (i + 1) + "/5 @ " + SystemClock.elapsedRealtime());
                          
 
                   
 try 
                   
 {
                       
 Thread.sleep(5000);
                  
  }
                   
 catch (InterruptedException e) 
                   
 {
                   
 }
               
 }
               
 Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());
               
 sendNotification("Received: " + extras.toString());
               
 Log.i(TAG, "Received: " + extras.toString());
           
 }
       
 }
       
 GcmBroadcastReceiver.completeWakefulIntent(intent);
   
 }

   
 //設定Notification要顯示的資訊與點擊Notification要開啟的頁面
   
 private void sendNotification(String msg) 
   
 {
       
 mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);        
       
 PendingIntent contentIntent = PendingIntent.getActivity(this, 0,new Intent(this, MainActivity.class) , 0);

       
 NotificationCompat.Builder mBuilder =
       
 new NotificationCompat.Builder(this)
       
 .setSmallIcon(R.drawable.ic_launcher)
       
 .setContentTitle("要顯示的標題名稱")
       
 .setStyle(new NotificationCompat.BigTextStyle()
       
 .bigText(msg))
       
 .setContentText(msg);
       
 
       
 mBuilder.setContentIntent(contentIntent);
       
 mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
   
 }
}


4. 最後一個步驟,上方新增的兩個class都是屬於接收到訊息後要做的事現在這個步驟是要向GCM註冊這支行動裝置,如果沒有註冊這個動作的話,GCM根本不知道要向哪個行動裝置進行推播發送,所以向GCM註冊裝置是一個很重要的環節。

首先在要做註冊的Activity裡添加註冊程式碼,小黑人以MainActivity作為範例。

public class MainActivity extends Activity
 
{
    //
只要把您的GCM註冊ID填入,其他部分皆一樣
   
 private String SENDER_ID = "GCM的註冊ID";
   
 private static final String TAG = "GCM :";
   
 public static final String EXTRA_MESSAGE = "message";
   
 public static final String PROPERTY_REG_ID = "registration_id";
   
 private static final String PROPERTY_APP_VERSION = "appVersion";
   
 private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
   
 private GoogleCloudMessaging gcm;
   
 private AtomicInteger msgId = new AtomicInteger();
   
 private Context context = this;
   
 private String regid;
   
 
       
 @Override
       
 protected void onCreate(Bundle savedInstanceState) 
       
 {
               
 super.onCreate(savedInstanceState);
               
 setContentView(R.layout.main);
               
 
               
 //確認是否已註冊過裝置,將這段放入您欲註冊的位置
               
 if (checkPlayServices()) 
               
 {
                   
 gcm = GoogleCloudMessaging.getInstance(this);
                   
 regid = getRegistrationId(context);

                   if (regid.isEmpty())
 
                  
 {
                       
 registerInBackground();
                   }
               
 }
  
              else 
               
 {
                   
 Log.i(TAG, "No valid Google Play Services APK found.");
               
 }
       
 }
       
 
       
 private boolean checkPlayServices() 
       
 {
           
 int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
       
 
           
 if (resultCode != ConnectionResult.SUCCESS) 
           
 {
               
 if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) 
               
 {
                   
 GooglePlayServicesUtil.getErrorDialog(resultCode, this,PLAY_SERVICES_RESOLUTION_REQUEST).show(); 
               
 } 
               
 else
               
 {
                   
 Log.i(TAG, "This device is not supported.");
                   
 finish();
               
 }
               
 return false;
           
 }
           
 return true;
       
 }
   
 
   
 private void storeRegistrationId(Context context, String regId) 
   
 {
       
 final SharedPreferences prefs = getGcmPreferences(context);
       
 int appVersion = getAppVersion(context);
       
 Log.i(TAG, "Saving regId on app version " + appVersion);
       
 SharedPreferences.Editor editor = prefs.edit();
       
 editor.putString(PROPERTY_REG_ID, regId);
       
 editor.putInt(PROPERTY_APP_VERSION, appVersion);
       
 editor.commit();
   
 }
   
 
   
 private String getRegistrationId(Context context) 
 
   {
       
 final SharedPreferences prefs = getGcmPreferences(context);
       
 String registrationId = prefs.getString(PROPERTY_REG_ID, "");
       
 
       
 if (registrationId.isEmpty()) 
       
 {
           
 Log.i(TAG, "Registration not found.");
    
        return "";
       
 } 
       
 int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
       
 int currentVersion = getAppVersion(context);
       
 if (registeredVersion != currentVersion) 
       
 {
           
 Log.i(TAG, "App version changed.");
           
 return "";
       
 }
       
 return registrationId;
   
 }
   
 
   
 private void registerInBackground() 
   
 {
       
 new AsyncTask<Void, Void, String>() 
       
 {
           
 @Override
           
 protected String doInBackground(Void... params) 
           
 {
               
 String msg = "";
               
 try 
               
 {
                   
 if (gcm == null) 
                   
 {
                       
 gcm = GoogleCloudMessaging.getInstance(context);
                   
 }
                   
 regid = gcm.register(SENDER_ID);
                   
 
                   
 msg = "Device registered, registration ID=" + regid;

                   
 sendRegistrationIdToBackend();
          
          storeRegistrationId(context, regid);
               
 }
               
 catch (IOException ex) 
               
 {
                   
 msg = "Error :" + ex.getMessage();
               
 }
               
 return msg;
           
 }

           
 @Override
           
 protected void onPostExecute(String msg) 
           
 {
           
 }
       
 }.execute(null, null, null);
   
 }
   
 
   
 private static int getAppVersion(Context context)
   
 {
       
 try 
       
 {
           
 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
                  
 
           
 return packageInfo.versionCode;
       
 } 
       
 catch (NameNotFoundException e) 
       
 {
           
 throw new RuntimeException("Could not get package name: " + e);
       
 }
   
 }
   
 
   
 private SharedPreferences getGcmPreferences(Context context) 
   
 {
       
 return getSharedPreferences(MainActivity.class.getSimpleName(), Context.MODE_PRIVATE);      
   
 }
   
 
   
 private void sendRegistrationIdToBackend() 
   
 {
   
     //裝置向GCM註冊完畢時進入
   
 }
}
 


以上為行動裝置向GCM註冊的分享文章,跟Google提供的範例程式一樣,小黑人也只是把程式碼貼上而已,只需要把GCM ID更換和註冊完畢後要做的動作變更,其他部分皆沒有修改。

以上4大步驟就是行動裝置接收GCM推播訊息的流程,雖然整體看起來蠻多程式碼的,但是需要修改的程式碼卻只有一小部分,大部分都只要將程式碼貼上即可,註冊動作與接收動作完成就可以收到GCM的推播通知囉,大家可以試試看在App裡加入推播機制~

謝謝大家,如有任何問題都可以和小黑人一起交流討論!

☆小黑人

21 則留言:

  1. 請問
    我的Android SDK Manager裡面的Extras資料夾沒有一個Google Play Services,那這樣我要去哪裡找這個檔案??

    回覆刪除
    回覆
    1. 您好,根據您的提問小黑人與您解答,
      如果Android SDK Manager裡的Extras沒有Google Play Services的話,
      那可能是您ADT(Android Developer Tools)的版本比較舊版,
      導致Android SDK Manager無法Update到一些附加開發工具,
      所以您需要先更新ADT再進行Google Play Services下載的動作。
      ADT的更新步驟如下 :
      Eclipse上方工具列選擇"Help" → 點選"Install New Software" →
      輸入"https://dl-ssl.google.com/android/eclipse/" → 更新"Developer Tools"。

      感謝您的提問。

      刪除
    2. 作者已經移除這則留言。

      刪除
    3. 作者已經移除這則留言。

      刪除
    4. 請問我照了程式碼後出現:
      MainActivity.java:
      GooglePlayServicesUtil 無法解析
      GooglePlayServicesUtil 無法解析
      GoogleCloudMessaging 無法解析成類型
      CPTabActivity 無法解析成類型

      刪除
    5. 您好,根據您的提問小黑人與您解釋,
      無法解析的狀況應該是Google Play Services專案沒有導入進來,
      導入Google Play Services專案步驟是:在您的專案上按右鍵,點選Properties,開啟視窗後左側列表點選Android,在下方Library區域裡按Add,將google-play-services_lib加入就可以囉,
      最後CPTabActivity 這個要更改成MainActivity,這是小黑人不小心把MainActivity名稱更改到,
      以上您可以再試試看。

      感謝您的留言!

      刪除
    6. 作者已經移除這則留言。

      刪除
    7. 作者已經移除這則留言。

      刪除
    8. 作者已經移除這則留言。

      刪除
  2. 看完這篇學到很多
    可以請問一下CPTabActivity.class
    是再做甚麼的嗎

    回覆刪除
    回覆
    1. 您好,非常感謝您的支持 ^^
      根據您的提問小黑人與您解釋,
      CPTabActivity.class其實就是MainActivity.class,
      小黑人可能不小心按到把Main改成別的字串...

      感謝您的提醒與留言,謝謝!

      刪除
  3. 作者已經移除這則留言。

    回覆刪除
  4. 請問GCM registration id 第4步驟註冊的部分,是自動註冊的嗎?
    如果是自動註冊要怎麼取得?

    回覆刪除
    回覆
    1. 您好,很抱歉這麼久才回覆您,
      根據您的提問小黑人與您解釋,
      應該是說您要先至GCM後台註冊一個推播裝置服務,然後GCM就會產生一個ID,這個ID就是所謂手機端的註冊ID。

      感謝您的提問!

      刪除
  5. 請問這程式碼完成後,基本執行會出現什麼結果??
    首先在要做註冊的Activity裡添加註冊程式碼,小黑人以MainActivity作為範例。
    如果像上述程式碼跟您建立在同個地方也可以??
    我開啟程式後,什麼事情都沒發生,我是用HTTP來傳送。

    回覆刪除
    回覆
    1. 您好,很抱歉這麼久才回覆您,
      根據您的提問小黑人與您解釋,
      主要關鍵步驟是"您GCM的註冊ID",應該是說您要先至GCM後台註冊一個推播裝置服務,然後GCM就會產生一個ID,這個ID就是所謂手機端的註冊ID,然後手機端在正常的情況下是都不會有任何狀況發生,直到GCM後台有發送出推播訊息後手機端才會藉由ID來接收並顯示推播訊息。

      感謝您的提問!

      刪除
  6. 您好~
    我用了您的程式一切都沒問題也能安裝成.apk
    但是在手機中開啟程式後,沒有任何反應也沒有得到registration_id
    我有哪邊步驟錯誤還是少做了甚麼??

    回覆刪除
    回覆
    1. 您好,很抱歉這麼久才回覆您,
      根據您的提問小黑人與您解釋,
      主要關鍵步驟是"您GCM的註冊ID",應該是說您要先至GCM後台註冊一個推播裝置服務,然後GCM就會產生一個ID,這個ID就是所謂手機端的註冊ID,然後手機端在正常的情況下是都不會有任何狀況發生,直到GCM後台有發送出推播訊息後手機端才會藉由ID來接收並顯示推播訊息。

      感謝您的提問!

      刪除
  7. 請問為什麼我把google-play-services_lib專案匯入後
    CPU使用率 直接飆到100% 然後celipse 就出現GC錯誤
    .記憶體不足的問題,試過重新安裝google-play-services了

    回覆刪除
  8. 您好,照著版主教學實作後,裝置一直沒有收到推播的訊息。
    要如何才會接收到推播的訊息呢?感謝版主回覆。

    回覆刪除
  9. 小黑人你好,最近也要來實作GCM,不過卡在"import android.support.v4.content.WakefulBroadcastReceiver;"

    是不是該class被Google給取消掉了?!

    回覆刪除

謝謝大家支持,有任何問題都可以和小黑人一起討論!