Android Push Notification โดยใช้ App Engine Template

Android Push Notification โดยใช้ App Engine Template Cover Image

Android GCM Push Notification Tutorial

บทความสอน Android บทความนี้ ผมขอนำเสนอสิ่งที่ Advanced ซักเล็กน้อย และคิดว่าน่าจะเป็นเรื่องต้นๆ ที่นักพัฒนา Android ทุกคนอยากจะรู้ นั่นก็คือ Push Notification โดยใช้ Google Cloud Messaging(GCM) ความยากของมัน ไม่ใช่ระบบที่ซับซ้อน แต่ว่ามันต้องใช้ความรู้ทั้งด้าน Client (Android) และความรู้ด้านเซิฟเวอร์ (Backend) ประกอบ ทำให้นักพัฒนาหลายๆคน ไม่สามารถทำ Push โดยใช้ GCM ได้

ย้อนกลับไปเมื่อก่อน ผมไม่เคยทำ Push Notification เองเลย เนื่องจากมีบริการ Backend As A Service (BAAS) หรือ Platform As A Service ต่างๆ มากมาย ใ้ห้ใช้ Push Notification ได้ ซึ่งมันสะดวกมากๆ ไม่ต้องยุ่งกับทางฝั่ง Server เลย เพียงแค่เรียกใช้ API ของทางผู้ให้บริการ ก็สามารถส่ง Push ไปให้ Client ได้แล้ว อย่างเช่น Parse.com หรือ App 42 Cloud: Shephertz

แต่ว่าหลังจากงาน Google I/O 2014 ที่เพิ่งจบลงไปไม่นาน ทาง Google ได้ปล่อย Android Studio Beta ออกมารวมถึงมีการทำ Template ที่ใช้ร่วมกับ Google App Engine ในการทำเป็น Backend (อันนี้ไม่แน่ใจว่ามีนานแล้ว หรือว่าผมเพิ่งสังเกตเห็นก็ไม่รู้นะ) เมื่อลองทดสอบ ลองเล่นแล้ว รู้สึกว่า เอ้อเว้ย มันง่ายแฮะ ก็เลยตัดสินใจ ทำบทความมาแชร์กันเลยครับ เผื่อตัวเองลืมด้วยแหละ :D

Table of Content

Step 0 : Prerequisite

ก่อนจะมาเริ่มบทความนี้ ผมคาดหวังว่าผู้อ่าน ควรจะมีความรู้ด้าน Java หรือว่า Android ในระดับหนึ่งแล้ว ฉะนั้นก็จะไม่พูดถึงในส่วนที่มันพื้นฐานมากนัก หากส่วนไหนไม่เข้าใจ แนะนำให้ศึกษาอ่านเพิ่มเติมนะครับ (ถ้าแค่ศึกษาต่อยอดจากของเดิม แต่ไม่รู้วิธี แล้วถามหาแต่ตัวอย่าง โดยยังไมไ่ด้ลองหาเอง ลองทำเอง ก็เลิกเป็น Programmer เถอะ)

เอาละ ส่วนบทความที่ผมเตรียมไว้ให้ พอที่จะหาได้ ก็อ่านๆตามนี้ประกอบละกันครับ

  • Google Cloud Messaging : เว็บต้นฉบับมีรายละเอียดทุกอย่าง ถ้าอ่านนะ ^^ หากคุณเป็น Android Dev ยังไงก็ต้องใช้เว็บนี้เป็นแหล่งอ้างอิงแน่นอน

  • Using Endpoints in an Android Client : ถึงบทความจะเก่าไปนิด แต่ก็พอเป็น guide ได้ครับ

  • Android Client Tutorial : สำหรับฝั่ง Client

  • App Engine Backend Templates : App Engine Template เอาไว้ทำ Backend โดยใช้ Template ใน Android Studio ลิงค์นี้มีอธิบายการทำค่อนข้างละเอียด ส่วนใหญ่บทความนี้ก็อ้างอิงมาจากลิงค์นี้เลยครับ

Step 1 : GCM Overview

Google Cloud Messaging for Android (GCM) คืออะไร? บางคนอาจจะยังไม่รู้ มันคือมันคือบริการฟรี ที่ Google จัดให้ ให้เราสามารถส่งข้อมูลจากเซิฟเวอร์ ไปยังเครื่อง Android ต่างๆได้ รวมถึงส่งข้อมูลจากเครื่อง Android กลับมาก็ยังได้

แล้วคุณสมบัติของ GCM มีอะไรบ้าง?

  • ต้องใช้ 3rd-Party เป็น Server (Backend) สำหรับส่งข้อความไปให้เครื่อง user

  • ใช้ GCM Cloud Connection Server (XMPP รูปแบบหนึ่ง) ในการรับข้อมูลจากเครื่อง user

  • App Android ที่จะทำการรับส่งข้อมูล ไม่จำเป็นต้องเปิดโปรแกรมตลอดเวลา ระบบจะรู้เองว่า มีข้อมูลหรือต้องส่งข้อความตอนไหน ผ่าน Intent broadcast

  • GCM เป็นแค่ raw data เท่านั้น คุณจะเอาไปใช้ทำอะไรก็เรื่องของคณ จะเป็น Toast, จะทำ Push หรือจะแค่ sync data ก็ up to you.

  • ต้องใช้ Android Version 2.2 ขึ้นไป และต้องติดตั้งแอพ Google Play หรือหากใช้ Emulator ก็ต้องสร้างแบบ Google APIs

  • ต้องทำการเซ้ทอัพ Google Account ก่อน หากเป็นเครื่องก่อน 3.0 แต่ว่าตั้งแต่ 4.0.4 ไม่ต้องแล้ว ใช้งานได้เลย

ข้อมูลส่วนนี้ผมอ้างอิงมาจากนี้ครับ GCM Overview

GCM Arch

จากรูปด้านบน แสดงถึงภาพรวมการทำงานของ GCM คือตัว GCM มันจะเป็นตัวกลางคอยส่งข้อมูลระหว่าง เซิฟเวอร์ กับเครื่อง user นั่นเอง แต่ว่าวิธีการทำงาน มันไม่ได้ดูง่ายๆแบบนั้น การทำงานของมันคือ เริ่มแรกเลยนะ

  1. ครั้งแรกที่เราสร้างแอพ Android ขึ้นมาซักตัวหนึ่ง แล้วอยากจะใช้ GCM เริ่มแรกก็ต้อง ทำการลงทะเบียนก่อน โดยใช้คลาส GoogleCloudMessaging (อันนี้เดี่ยวพูดถึงอีกที ดูภาพรวมไปก่อน) เมื่อลงทะเบียนว่าอยากใช้ GCM แล้วนะ โดยส่ง sender Id และ applicationId ไปให้ ทาง Google เมื่อทาง Google ได้รับแล้ว ก็จะส่งรหัสกลับมาให้เราเป็น registration id ไอ้รหัสที่ Google ส่งมานี้แหละ เดี่ยวจะได้ใช้ต่อไปแน่นอน

  2. ทีนี้เมื่อทางเครื่อง client หรือก็คือเครื่องยูเซอร์แต่ละคน เมื่อได้ registration id แล้ว มันก็จะ่ส่งไปให้ทาง Server (3rd-Party) คนละตัวกับ GCM นะ โดยสิ่งที่เราจะต้องทำในส่วนฝั่ง Server ขั้นแรก ก็คือ เก็บรหัส registration id ที่ client ส่งมานี้แหละ เอาไว้ เพื่อเวลาจะส่ง Push Notification เราจะได้รู้ว่า จะต้องส่งไปที่เครื่องไหน

โอเค เมื่อรู้ concept คร่าวๆ กันไปแล้ว (ซึ่งตัวผมเอง ก็ยังไม่ค่อยเข้าใจเหมือนกัน ฮ่าๆ) ก็ไปเริ่มปฎิบัติเลยดีกว่า

Step 2 : Setup Google Play Service

ก่อนที่เราจะสามารถใช้ Google Play Service ได้นั้น เราจำเป็นจะต้องดาวน์โหลดตัว SDK มาก่อนนะครับ (อันนี้คิดว่า ส่วนใหญ่น่าจะมีกันแล้ว ก็ข้ามไป Step ถัดไปได้้ครับ)

เริ่มแรก ควรใช้ SDK Tools เวอร์ชั่นล่าสุดครับ เปิด Android SDK Manager แล้วให้แน่ใจว่า ดาวน์โหลด SDK Tools เวอร์ชันล่าสุดแล้ว ตามภาพด้านล่าง

Image of Android SDK

Image og Play Service

อย่าง ณ เวลาที่เขียนบทความ SDK Package ผมก็จะมีเวอร์ชั่นดังนี้

  • Android SDK Tools เวอร์ชั่น 23.0.2
  • Android SDK Platform Tool เวอร์ชั่น 20
  • Android Build Tool เวอร์ชั่น 20
  • Android Support Library เวอร์ชั่น 20
  • Android Support Repository เวอร์ชั่น 6
  • Google Repository เวอร์ชั่น 9
  • Google Play Services เวอร์ชั่น 18

ต่อมา เรามีแค่ตัว SDK ยังไม่สามารถใช้ Library ในโปรเจ็คเราได้ ต้องทำการอ้างถึง Library กันก่อน (Refering Library Project)

สำหรับบน Eclipse

การ Refer Library ทำได้โดยการคลิกขวาที่ Project -> เลือก Properties -> แท็ป Android แล้วก็เลือก Add Library ที่ต้องการ

Add Library

หรืออ่านเพิ่มเติมที่นี่ วิธีการเพิ่ม Library เข้าไปในโปรเจ็ค Android

จากนั้นเปิดไฟล์ AndroidManifest.xml แล้วเพิ่มด้านล่างนี้ลงไปก่อนปิดแท็ก application

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

สำหรับ Android Studio

ให้เปิดไฟล์ build.gradle ในโมดูล (คนละตัวกับใน root project นะ) แล้วก็อปปี้ com.google.android.gms:play-service:5.0.77 ไปใส่ในแท็ก dependencies ตามข้างล่างนี้

apply plugin: 'com.android.application'
...

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:20.+'
    compile 'com.google.android.gms:play-services:5.0.77'
}

ทำการกด Sync Project with Gradle Files เพื่อให้ Gradle โหลด dependencies มาลงไว้ใน local ต่อมาก็เปิด AndroidManifest.xml แล้วเพิ่มลงไปก่อนปิดแท็ก application

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

หากใช้ Proguard ก็เพิ่มนี้ลงไปที่ไฟล์ proguard-project.txt

-keep class * extends java.util.ListResourceBundle {
    protected Object[][] getContents();
}

-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
    public static final *** NULL;
}

-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
    @com.google.android.gms.common.annotation.KeepName *;
}

-keepnames class * implements android.os.Parcelable {
    public static final ** CREATOR;
}

หากใช้ Android Studio อย่าลืมเปลี่ยน BuildTypes runProguard เป็น true ด้วยนะครับ

Step 3 : Create Google APIs Console

การติดต่อ Google APIs Console ทำได้โดยการเข้าเว็บ Google Developers Console (ใครยังไม่เคยใช้งาน ก็สมัครซะ ส่วนนี้ผมไม่ทำวิธีสมัครให้นะครับ ขี้เกียจ ^^ )

กด Create Project แล้วตั้งชื่อโปรเจ็คตามต้องการเลย ส่วน project id จะให้มัน gen ให้หรือตั้งเองก็ได้ แต่ขอให้มันไม่ซ้ำใครก็พอ

Create Project

กด Create โลดดด รอมันสร้างแปปนึง แล้วมันจะ redirect เราไปหน้าของโปรเจ็คที่สร้างเมื่อกี้

สิ่งที่เราจะต้องจด และเอาไปใช้นั่นก็คือ รหัส Project ID และ รหัส Project Number

ดูที่มุมซ้ายมือ เลือก APIS&AUTH -> APIs ดูว่า เราได้เลือก Google Cloud Messaging for Android เป็น ON แล้วหรือยัง ถ้ายังก็เลือกเป็น ON ซะซิ รอช้าทำไม?

ต่อมา เลือกมาช่องถัดลงไป Credentials ตรงส่วน Public API Access เลือก Create new key -> Server Key

create new key

แล้วใส่ 0.0.0.0/0 ลงไปในช่องเลย อย่าถามว่าทำไมต้องเป็นค่านี้ ผมก็ไม่รู้เหมือนกัน เข้าใจตรงกันนะ :D

create new key

ที่่นี้เราก็จะได้ API KEY ที่ขึ้นต้นด้วย AIzaS.... ไรพวกนี้แหละ เก็บไว้เลย เอาไปใช้ต่อในโปรเจ็ค

โอเค ได้เวลาลงมือสร้างโปรเจ็คซักที เริ่มต้นสร้างโปรเจ็ค Android เลยฮะ จะตั้งชื่อว่าอะไรก็ตามสบาย ผ่านไปเลย มาถึงหน้าแรก หลังจากสร้างเลยละกัน

Create Project

อุ้ย! ตัวแดง error รึเปล่าหนอ?

Gralde Sync

Note: เอาภาพมาให้ดู Gradle Sync Error จริงๆ มันก็ไม่มีไรมาก พอดีผมเลือก targetSDK เป็น 20 ก็แค่ปรับ appcompat ให้เวอร์ชันเดียวกับ targetSDK ซะ

compile 'com.android.support:appcompat-v7:20.+'

แล้วก็ทิ้งไว้แค่นี้ก่อนครับ ยังไม่ต้องทำอะไรต่อ ไปทำฝั่ง Server ก่อน แล้วเดี่ยวค่อยกลับมาต่อ ฝั่ง Client

Step 4 : Create GCM Server

ต่อมาทำการทำฝั่ง Server(Backend) กันบ้าง ก็สร้าง Module ใหม่เลย เลือก New -> Module จากนั้นเลือก App Engine Backend with Google Cloud Messaging ซึ่งเป็น Module Template ที่ทาง Google จัดไว้ให้ ทำงานร่วมกับ Google App Engine

Create Module

เมื่อเลือกแล้ว ก็ตั้งชื่อ Module และ Package Name

Create Module2

กด Finish รอ gradle ทำการโหลด Library ที่จำเป็นมาก่อน เช่นพวก appengine-java-sdk, guava เป็นต้น (รอพักนึงเลย ไฟล์ใหญ่พอสมควร ขึ้นอยู่กับความเร็วเนต)

เมื่อโหลดอะไรเรียบร้อยแล้ว ลอง Debug ดูดีกว่า เลือกเป็น Module Backend ของเรา จากนั้น Run ดูครับ

Backend

แล้วทดสอบเข้าหน้าเว็บ http://localhost:8080/ (ใครเคยเขียน JSP&Servlets รัน Tomcat อาจจะคุ้นๆ Port นี้ ^^) ได้หน้าตาประมาณนี้

Localhost

OK ทางฝั่ง Server รันได้ ทีนี้ กลับไป Client กันอีกรอบ เพื่อ implements ตัว Server นี่แหละ อ้อก่อนกลับไป Client ลืมเปลี่ยน API ครับ แหะๆ

เข้าไปที่ webapp/WEB-INF/appengine-web.xml แล้วเอา API KEY ที่ได้จาก Step 3 : Create Google APIs Console บอกให้จด จดไว้มั้ยครับ ฮ่าๆ ใครลืมก็กลับไปดูใหม่ มาใส่แบบนี้

<property name="gcm.api.key" value="YOUR_KEY_HERE"/>

เป็น

<property name="gcm.api.key" value="AIza..."/>

และก็สังเกตเห็นแท็ก

<application>myApplicationId</application>

ตรงนี้มั้ยครับ ก็ให้เอา Project ID (ชื่อที่เราตั้งเพือ่ไม่ให้ซ้ำ) มาใส่ ใครลืม ก็กลับไปดู Step 3 : Create Google APIs Console โอเค อย่างของผมก็จะได้เป็น

<application>dynamic-heading-634</application>

เอ๊ะๆ แล้วบางคนสงสัย Project Number เอาไปใช้ส่วนไหนเนี่ย จำเยอะเดี๋ยวลืม! เดี่ยวตัว Project Number จะเอาไว้ใช้ในส่วน ของ Client ครับ จะเป็น SENDER_ID

โอเค ในส่วน Server ก็เสร็จในระดับหนึ่งละ วาร์ปกลับไปฝั่ง Client ต่อ ไปที่โมดูล app ครับ เปิดไฟล์ MainActivity.java มาเหมือนเดิม

Step 5 : Create GCM Client

หลักจากค้างฝั่ง Client ไว้ ก็กลับมาต่อ โดยการสร้าง inner คลาสขึ้นมาคลาสนึงชื่อว่า GcmRegistration อยู่ใน MainActivity.java ดังนี้

package com.devahoy.ahoysimplepush;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;

import com.devahoy.ahoysimplepush.backend.registration.Registration;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.extensions.android.json.AndroidJsonFactory;
import com.google.api.client.googleapis.services.AbstractGoogleClientRequest;
import com.google.api.client.googleapis.services.GoogleClientRequestInitializer;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;


public class MainActivity extends ActionBarActivity {

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

        new GcmRegistration().execute(this);
    }

     class GcmRegistration extends AsyncTask<Context, Void, String> {

        private GoogleCloudMessaging mGcm;
        private Context mContext;
        private Registration mRegister;

        private static final String SENDER_ID = "YOUR_SENDER_ID";

        public GcmRegistration() {
            Registration.Builder builder =
                    new Registration.Builder(AndroidHttp.newCompatibleTransport(),
                            new AndroidJsonFactory(), null)
                            .setRootUrl("http://10.0.2.2:8080/_ah/api/")
                            .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
                                @Override
                                public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest)
                                        throws IOException {
                                    abstractGoogleClientRequest.setDisableGZipContent(true);
                                }
                            });

            mRegister = builder.build();
        }

         @Override
         protected String doInBackground(Context... params) {
             mContext = params[0];

             String msg = "";
             try {
                 if (mGcm == null) {
                     mGcm = GoogleCloudMessaging.getInstance(mContext);
                 }
                 String regId = mGcm.register(SENDER_ID);
                 msg = "Registration ID : " + regId;

                 mRegister.register(regId).execute();

             } catch (IOException ex) {
                 ex.printStackTrace();
                 msg = "Error: " + ex.getMessage();
             }
             return msg;
         }

         @Override
         protected void onPostExecute(String msg) {
             Toast.makeText(mContext, msg, Toast.LENGTH_LONG).show();
             Logger.getLogger("REGISTRATION").log(Level.INFO, msg);
         }
    }

}

โค๊ดด้านบน อย่าลืมเปลี่ยน SENDER_ID ด้วยนะครับ แล้วก็สั่ง new GcmRegistration().execute(this); ที่เมธอด onCreate()

Step 6 : Test Emulator

มาถึงขั้นตอนการทดสอบว่าแอพเราใช้ได้จริงไหม จุดประสงค์ของการเทสคือ ทดสอบว่าเมื่อเวลาเราเปิดแอพ โปรแกรมจะทำการ register กับทาง GCM โดยใช้ SENDERID ของเรา เพื่อให้รู้ว่า อะ ไอ้เจ้าเครื่องนี้นะ จะสามารถรับ Push จาก SENDERID ได้ และผู้ส่ง ก็จะรู้ว่าจะส่งให้เครื่องไหน จาก registration ID มาลองทดสอบดูเลยครับ

การทดสอบด้วย Emulator จำเป็นต้อง System แบบ Google APIs นะครับ

Android Create Emulator

เมื่อรันแล้ว จะเห็น Toast ขึ้นมาแสดงว่า Register เรียบร้อยแล้ว ได้ Registration ID แบบนี้

Registration ID

Step 7 : Show Push Notification

เอาละ เมื่อเราลงทะเบียน และได้ Registration ID มาแล้ว คราวนี้ฝั่ง Server ก็สามารถ Push ข้อความมาหาเราได้ละ

แต่ฝั่ง Client ยังจำเป็นต้องมีอีก 2 คลาสคือ ตัว BroadCastService และตัว IntentReceiver ฉะนั้น ผมก็สร้าง 2 คลาส ชื่อว่า GcmIntentService และ GcmBroadcastReceiver ดังนี้

คลาส GcmBroadcastReceiver.java

package com.devahoy.ahoysimplepush;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // Explicitly specify that GcmIntentService will handle the intent.
        ComponentName comp = new ComponentName(context.getPackageName(),
                GcmIntentService.class.getName());
        // Start the service, keeping the device awake while it is launching.
        startWakefulService(context, (intent.setComponent(comp)));
        setResultCode(Activity.RESULT_OK);
    }
}

คลาส GcmIntentService.java

package com.devahoy.ahoysimplepush;

import android.app.IntentService;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import com.google.android.gms.gcm.GoogleCloudMessaging;

import java.util.logging.Level;
import java.util.logging.Logger;

public class GcmIntentService extends IntentService {

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

    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        // The getMessageType() intent parameter must be the intent you received
        // in your BroadcastReceiver.
        String messageType = gcm.getMessageType(intent);

        if (extras != null && !extras.isEmpty()) {  // has effect of unparcelling Bundle
            // Since we're not using two way messaging, this is all we really to check for
            if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
                Logger.getLogger("GCM_RECEIVED").log(Level.INFO, extras.toString());

                showToast(extras.getString("message"));
            }
        }
        GcmBroadcastReceiver.completeWakefulIntent(intent);
    }

    protected void showToast(final String message) {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
            }
        });
    }
}

ต่อมาเปิดไฟล์ AndroidManifest.xml แล้วเพิ่มโค๊ดข้างล่างนี้ก่อนปิดแท็ก application

<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="YOUR_PACKAGE_NAME" />
    </intent-filter>
</receiver>
<service android:name=".GcmIntentService" />

เปลี่ยน YOUR_PACKAGE_NAME ด้วยนะครับ

ดูในเรื่อง Implementing GCM Client ประกอบด้วยนะครับ จะเห็นว่า พวก permission มันมาได้ไง มันถูกสร้างตอนที่เราสร้างโมดูลเพื่อทำส่วน Backend นะครับ

OK Build และรันแอพใหม่อีกรอบ

ยังจำหน้าเว็บได้อยู่มั้ย? เรายังรัน backend ไว้อยู่ ยังไ่ม่ได้ปิด ฉะนั้นลองเข้า http://localhost:8080/ ดูใหม่ แล้วทดสอบ พิมพ์ข้อความอะไรก็ได้ลงไป กด Send Message แล้วดูหน้าจอ Emulator ว่ามี Message เข้ามั้ย?

Push

OK เรียบร้อย ส่งข้อความจาก Server ไปหา Client ได้แล้ว!!!

Step 8 : Deploy to App Engine.

ขึ้นตอนสุดท้ายละครับ คือการ Deploy ฝั่ง Server ขึ้นไปอยู่ที่ App Engine ซะ จากที่เราเทสแค่ในเครื่องตัวเอง คราวนี้จะได้นำ Server ไปไว้ของจริงกันละ เริ่มด้วย เปิด Terminal ที่อยู่ใน Android Studio แล้วพิมพ์คำสั่งนี้ลงไป

./gradlew backend:appengineUpdate

appengineUpdate เป็นคำสั่งสำหรับไฟล์ ของ Gradle Engine Plugin ครับ หากต้องการดูรายละเอียดเพิ่ม ก็อ่านนี้ Gradle App Engine Plugin

Terminal

ดูด้วยนะต้องอยู่ที่ root project นะครับ เพราะมันจะมีไฟล์ gradlew อยู่ ไ่ม่ใช่ใน app/ นะ!

ขณะ Gradle กำลัง build อยู่จะขึ้น Popup ใน Browser มาให้เรากด Sign-In รวมถึงขออนุญาตใช้สิทธิ์การใช้ App Engine ด้วย

Accept Permission

เมื่อเรายอมรับ ก็จะได้โค๊ดนี้มา

Copy to Terminal

ก็อบโค๊ดที่เห็นจากหน้า Browser มาใส่ใน Terminal ของ Android Studio

จากนั้นก็นอนตีพุงรอมัน Deploy ขึ้น App Engine ครับ สามารถเข้าใช้งานได้โดยใช้ project Id เป็น subdomain ของ appspot.com แบบนี้ครับ your-project-id.appspot.com

สุดท้ายกลับมาที่คลาส MainActivity.java ตรงส่วน Registration.Builder สังเกตว่าเราใช้ setRootUrl เป็น 10.0.2.2:8080 ซึ่งมันคือ localhost แต่ว่า Backend เราอัพลง App Engine แล้ว ฉะนั้น ก็เปลี่ยนจาก

public GcmRegistration() {
    Registration.Builder builder =
            new Registration.Builder(AndroidHttp.newCompatibleTransport(),
                    new AndroidJsonFactory(), null)
                    .setRootUrl("http://10.0.2.2:8080/_ah/api/")
                    .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
                        @Override
                        public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest)
                                throws IOException {
                            abstractGoogleClientRequest.setDisableGZipContent(true);
                        }
                    });

    mRegister = builder.build();
}

เหลือเพียงแค่

public GcmRegistration() {
    Registration.Builder builder =
            new Registration.Builder(AndroidHttp.newCompatibleTransport(),
                    new AndroidJsonFactory(), null);
    mRegister = builder.build();
}

กดรันแอพ เพื่อลงทะเบียนขอ registration id ใหม่ และก็เข้าหน้าเว็บของ App Engine ทดลองส่ง message จาก App Engine ไปหาเครื่อง Client ดูนะครับ อาจมีอาการ Delay เล็กน้อยครับ จบแล้ว!

ขอให้สนุกกับการ Coding นะครับ :D

Troubleshooting

ปัญหาส่วนใหญ่ที่พบบ่อยๆ คือ

Error Code: -32099 [com.google.android.gcm.server.InvalidRequestException: HTTP Status Code: 401]

พบเมื่อเวลากด Push จะส่งจาก Backend ไป Client แล้วขึ้น error แบบนี้ แสดงว่า มีการตั้งค่า SENDER_ID ผิด หรือไม่ก็ลืมขอ Google APIs ดูให้แน่ใจว่าขอแล้ว และเปลี่ยน API ในไฟล์ appengine-web.xml ด้วย

  • Emulator ไม่ได้รับ Push Notification : ต้องอย่าลืมว่า เวลาสร้าง Emulator ต้องสร้างแบบใช้ Google APIs นะครับ หากไม่มี ก็ต้องไปโหลดเพิ่มใน Android SDK Manager

  • bash: ./gradlew: No such file or directory เกิดในกรณีตอนที่คุณจะ deploy ไป App Engine แล้วรันสคริป gradlew ไม่ถูกต้อง เนื่องจากอาจจะไปรัน โดยอยู่ที่ path ของโมดูล ต้องรันจาก Root Project เนื่องจากไฟล์ gradlew มันอยู่ที่ Root Project

Chai

Chai Phonbopit : Developer แห่งหนึ่ง • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Node.js, JavaScript และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด