2016年2月28日 星期日

Google cloud messaging 2

Google Cloud Messaging
pushing can be two way:
1.server, send message to the app,tell the app to pull the data
2.server directly send the data to the app

GCM can help us to do the pushing

now,we crate a Google Cloud Messaging project


step1,

step2
Click on "Create Project".
Supply a project name and click "Create".
Once the project has been created, a page appears that displays your project ID and project number. For example, Project Number: 670330094152.

step3
go
click Get a configuration file
to het the config file
Add a config file to your project part
step4
Copy the config file into your App/ or Mobile/ directory of your Android Studio project.
step5
open the android studio termial plan

step6
move "C:\Udacity\otherpeople_example\Sunshine_map\Sunshine_map\app\google-services.json" "app\"  
step7
Add the dependency to your project-level build.gradle file with the line: classpath 'com.google.gms:google-services:1.5.0-beta2'
Add the plugin to your app-level build.gradle file with the line: apply plugin: 'com.google.gms.google-services'

step8

To enable the GCM service:
In the sidebar on the left, select APIs & auth.
In the displayed list of APIs, turn the Google Cloud Messaging for Android toggle to ON.

step9

To obtain an API key:
In the sidebar on the left, select APIs & auth > Credentials.
Under Public API access, click Create new key.
In the Create a new key dialog, click Server key.
In the resulting configuration dialog, supply your server's IP address. For testing purposes, you can use 0.0.0.0/0.

Click Create.
In the refreshed page, copy the API key. You will need the API key later on to perform authentication in your app server.

AIzaSyDJPhPIXchNt3uQ8E7Hiy5a26xSfCKw8ac


**one import thing is we need to install the google play service and google repository in the SDK manager


step10
add
dependencies {
 compile "com.google.android.gms:play-services:8.4.0"
}


new we can use it

it is becuase the GCM must have googleplayu service in the device,so we need to check is the google play service alailable in the device:



check is the google play service alailable in the device:

when should we check?
we shouldcan check at onCreate onResume.
the onResume check is must,becuase we don’t kknow what will the user do after leave the app,than back to the app.



the code use to check


private boolean checkPlayServices() {
       GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
       int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);
       if (resultCode != ConnectionResult.SUCCESS) {
           if (apiAvailability.isUserResolvableError(resultCode)) {
               apiAvailability.getErrorDialog(this, resultCode,
                       PLAY_SERVICES_RESOLUTION_REQUEST).show();
           } else {
               Log.i(LOG_TAG, "This device is not supported.");
               finish();
           }
           return false;
       }
       return true;
   }


now We add it to the OnCreate()

here is the complete result:



add the permission for the GCM
<uses-permission android:name="android.permission.INTERNET" />
because we need to access the internet

<uses-permission android:name="android.permission.WAKE_LOCK" />
it allow Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming.


從Android SDK官網可以看到wakelock分成四種

1. PARTIAL_WAKE_LOCK
讓CPU持續運行, 但不讓螢幕或鍵盤背光亮起
2. SCREEN_DIM_WAKE_LOCK
點亮螢幕, 但只有灰暗程度
3. SCREEN_BRIGHT_WAKE_LOCK
點亮螢幕, 亮度全開
4. FULL_WAKE_LOCK
點亮螢幕和鍵盤背光, 亮度全開


because we will use the provided GCM receiver, it extends wakeful brodcast receiver, this reciver need the .WAKE_LOCK permission


5.
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
allow  App receives GCM messages


6.Message
it make other app can’t receive our message

<permission android:name="完整package名稱.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="完整package名稱.permission.C2D_MESSAGE" />
...
...
 
<!-- 使用GCM -->
<uses-permission android:name="完整package名稱.permission.C2D_MESSAGE" />


Add the calss declare to the manifest

for the GCM receiver,handle the down stream message from GCM to the app


<receiver
           android:name="com.google.android.gms.gcm.GcmReceiver"
           android:exported="true"
           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.example.android.sunshine.app" />
           </intent-filter>
       </receiver>




some basice information of intent filter:
<action>
talk about what type of action should this activity,reveiver,service can take
<category>
set the type of this activity,reveiver,service
<data>
the data type that can be handle

example:
<intent-filter>
               <action android:name="com.google.android.c2dm.intent.RECEIVE" />
               <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
               <category android:name="com.example.android.sunshine.app" />
           </intent-filter>

that mean that receiver can take action for

"com.google.android.c2dm.intent.RECEIVE" and
"com.google.android.c2dm.intent.REGISTRATION"

the type of the receiver is

"com.example.android.sunshine.app"


how system to compare the intent-filter?

when a impact intent send out ,the system comapre:

first,the action
the the filter must have the action that define in the intent

second category

the the filter must have the categorythat define in the category,if the intent do not define the categoryu,the mean the category is "android.intent.category.DEFAULT",so, the intent-filter should have
"android.intent.category.DEFAULT"


third data
compare the type,scheme,authority,path

some basice information of intent filter finish



android:permission="com.google.android.c2dm.permission.SEND"
mean we required the the send permission. anything send message to the receiver,need to have the send permission


Listener


<service
           android:name=".gcm.MyGcmListenerService"
           android:exported="false" >
           <intent-filter>
               <action android:name="com.google.android.c2dm.intent.RECEIVE" />
           </intent-filter>
       </service>



<service
           android:name=".gcm.MyInstanceIDListenerService"
           android:exported="false">
           <intent-filter>
               <action android:name="com.google.android.gms.iid.InstanceID"/>
           </intent-filter>
       </service>


<service
           android:name=".gcm.RegistrationIntentService"
           android:exported="false" >
       </service>



these three service need to create by our self

Now, we start to create them

MyGcmListenerService

MyInstanceIDListenerService

RegistrationIntentService


Important ***why these three service?
1.Android application needs register with GCM connection servers before it can receive messages.

2.When an app registers, it receives a registration token

3.the app sends it to the app server. The client app should store a boolean value indicating whether the registration token has been sent to the server.

more detail:
1.The Instance ID service issues an InstanceID when your app comes online. The InstanceID is backed by a public/private key pair with the private key stored on the local device and the public key registered with the Instance ID service.

2.
Your app can request a fresh InstanceID whenever needed using the getID() method. Your app can store it on your server if you have one that supports your app.
we already explain 3


4.


Tokens are unique and secure, but your app or the Instance ID service may need to refresh tokens in the event of a security issue or when a user uninstalls and reinstalls your app during device restoration. Maybe the token is nor present or expire. Your app must implement a listener to respond to token refresh requests from the Instance ID service.

so,we need MyInstanceIDListenerService,Google play service with auto start this services to refresh the token when need.How we get the new token?



if we want to get the token,we nedd to call instanceID.getToken,but it ca’y run in UI thread,so we need RegistrationIntentService

To receive simple downstream messages, use a service that extendsGcmListenerService to handle messages captured by GcmReceiver,so we need MyGcmListenerService


public static final String SENT_TOKEN_TO_SERVER = "sentTokenToServer";

example of RegistrationIntentService

//ues to get the Token
public class RegistrationIntentService extends IntentService {
   private static final String TAG = "RegIntentService";

   public RegistrationIntentService() {
       super(TAG);
   }

   @Override
   protected void onHandleIntent(Intent intent) {
       SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);

       try {
           // In the (unlikely) event that multiple refresh operations occur simultaneously,
           // ensure that they are processed sequentially.
           synchronized (TAG) {
               // Initially this call goes out to the network to retrieve the token, subsequent calls
               // are local.
               InstanceID instanceID = InstanceID.getInstance(this);
               String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
                       GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
               sendRegistrationToServer(token);

               // You should store a boolean that indicates whether the generated token has been
               // sent to your server. If the boolean is false, send the token to your server,
               // otherwise your server should have already received the token.
               sharedPreferences.edit().putBoolean(MainActivity.SENT_TOKEN_TO_SERVER, true).apply();
           }
       } catch (Exception e) {
           Log.d(TAG, "Failed to complete token refresh", e);

           // If an exception happens while fetching the new token or updating our registration data
           // on a third-party server, this ensures that we'll attempt the update at a later time.
           sharedPreferences.edit().putBoolean(MainActivity.SENT_TOKEN_TO_SERVER, false).apply();
       }
   }

   /**
    * Normally, you would want to persist the registration to third-party servers. Because we do
    * not have a server, and are faking it with a website, you'll want to log the token instead.
    * That way you can see the value in logcat, and note it for future use in the website.
    *
    * @param token The new token.
    */
   private void sendRegistrationToServer(String token) {
       Log.i(TAG, "GCM Registration Token: " + token);
   }
}


example of MyInstanceIDListenerService

public class MyInstanceIDListenerService extends InstanceIDListenerService {
   private static final String TAG = "MyInstanceIDLS";

   /**
    * Called if InstanceID token is updated. This may occur if the security of
    * the previous token had been compromised. This call is initiated by the
    * InstanceID provider.
    */
   @Override
   public void onTokenRefresh() {
       // Fetch updated Instance ID token.
       Intent intent = new Intent(this, RegistrationIntentService.class);
       startService(intent);
   }
}
Although we add the above two service,but we have not use them to get the token yet,we shuld use it at the main activity,after we check we have the google play service.

conecpt:
because if we already have atoken, because we save true for the key SENT_TOKEN_TO_SERVER,if false,that mean not yest get the toke,so we need to get the token now by the RegistrationIntentService.


if (checkPlayServices()) {
           // Because this is the initial creation of the app, we'll want to be certain we have
           // a token. If we do not, then we will start the IntentService that will register this
           // application with GCM.
           SharedPreferences sharedPreferences =
                   PreferenceManager.getDefaultSharedPreferences(this);
           boolean sentToken = sharedPreferences.getBoolean(SENT_TOKEN_TO_SERVER, false);
           if (!sentToken) {
               Intent intent = new Intent(this, RegistrationIntentService.class);
               startService(intent);
           }
       }


the full project:



Now we register and get the token,but we still can not get the down stream mssage
we need MyGcmListenerService.

we want when recive the GCM message,we show a noticication.



public class MyGcmListenerService extends GcmListenerService {

   private static final String TAG = "MyGcmListenerService";

   private static final String EXTRA_DATA = "data";
   private static final String EXTRA_WEATHER = "weather";
   private static final String EXTRA_LOCATION = "location";

   public static final int NOTIFICATION_ID = 1;

   /**
    * Called when message is received.
    *
    * @param from SenderID of the sender.
    * @param data Data bundle containing message data as key/value pairs.
    *             For Set of keys use data.keySet().
    */
   @Override
   public void onMessageReceived(String from, Bundle data) {
       // Time to unparcel the bundle!
       if (!data.isEmpty()) {
           // TODO: gcm_default sender ID comes from the API console
           String senderId = getString(R.string.gcm_defaultSenderId);
           if (senderId.length() == 0) {
               Toast.makeText(this, "SenderID string needs to be set", Toast.LENGTH_LONG).show();
           }
           // Not a bad idea to check that the message is coming from your server.
           if ((senderId).equals(from)) {
               // Process message and then post a notification of the received message.
               try {
                   JSONObject jsonObject = new JSONObject(data.getString(EXTRA_DATA));
                   String weather = jsonObject.getString(EXTRA_WEATHER);
                   String location = jsonObject.getString(EXTRA_LOCATION);
                   String alert =
                           String.format(getString(R.string.gcm_weather_alert), weather, location);
                   sendNotification(alert);
               } catch (JSONException e) {
                   // JSON parsing failed, so we just let this message go, since GCM is not one
                   // of our critical features.
               }
           }
           Log.i(TAG, "Received: " + data.toString());
       }
   }

   /**
    *  Put the message into a notification and post it.
    *  This is just one simple example of what you might choose to do with a GCM message.
    *
    * @param message The alert message to be posted.
    */
   private void sendNotification(String message) {
       NotificationManager mNotificationManager =
               (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
       PendingIntent contentIntent =
               PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);

       // Notifications using both a large and a small icon (which yours should!) need the large
       // icon as a bitmap. So we need to create that here from the resource ID, and pass the
       // object along in our notification builder. Generally, you want to use the app icon as the
       // small icon, so that users understand what app is triggering this notification.
       Bitmap largeIcon = BitmapFactory.decodeResource(this.getResources(), R.drawable.art_storm);
       NotificationCompat.Builder mBuilder =
               new NotificationCompat.Builder(this)
                       .setSmallIcon(R.drawable.art_clear)
                       .setLargeIcon(largeIcon)
                       .setContentTitle("Weather Alert!")
                       .setStyle(new NotificationCompat.BigTextStyle().bigText(message))
                       .setContentText(message)
                       .setPriority(NotificationCompat.PRIORITY_HIGH);
       mBuilder.setContentIntent(contentIntent);
       mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
   }
}



String:

<!-- Used to form a severe weather alert that reads "Heads up: <weather> in <location>!" -->
   <string name="gcm_weather_alert">Heads up: %1$s in %2$s!</string>


<string name="gcm_defaultSenderId" translatable="false"></string>

gcm_defaultSenderId ge tfrom :
>my-sunshine-1221 is the project ID
gcm_defaultSenderId=647520859929



Test:
go to the get confg file page
get the key and

id get from the log
input to the website


still have upstraem message,we can try it later!!!!