1. 程式人生 > >Android推送 利用REST API實現從客戶端推送(百度雲推送)

Android推送 利用REST API實現從客戶端推送(百度雲推送)

隨著谷歌開發者大會在中國的成功舉行,也帶來激動人心的訊息,2016年12月8日Google Developers中文網站正式釋出了。以後學習Android,獲取Android Studio、安卓最新版SDK等最新開發資源也變得更加輕鬆了。直接訪問Google Developers官網 https://developers.google.cn/ 即可。好了,激動之餘讓我們來看看今天為大家帶來的關於訊息推送的博文,這次還是使用Android Studio進行開發,不過Eclipse的整合開發也和下面要介紹的內容差不太多。按照慣例,還是先來看看程式的效果圖:


1. 整合SDK前的準備工作

1.1 開發者身份認證

登入百度雲推送官網後,點選建立應用,如果沒有申請過,則會跳轉到認證頁面,通過填寫一系列的認證資訊,完成認證。


1.2 下載SDK

官網下載最新版的SDk


1.3 建立專案

1.3.1 在編輯器中建立好專案

利用Android Studio新建好專案,並記好包名。

1.3.2 在百度雲推送官網建立應用

再次點選百度雲推送“建立應用”的選單項,成功跳轉後點擊“建立新應用”按鈕後開始建立應用,過程中需要填寫專案的包名,就不再細說了。最後得到下圖的API Key等資訊,後面馬上就要派上用場了。


2. 如何在專案中整合SDK

整合的步驟和呼叫SDK的方法都可以在文件中心查閱,還算是寫的比較詳細的了。

2.1 匯入Jar包

解壓好之前下載的SDK,把libs目錄下的 pushservice-5.5.0.50.jar 複製到工程目錄下的libs資料夾中。

2.2 匯入so檔案

在工程目錄 src/main 下新建一個jniLibs資料夾,把libs目錄下除Jar包以外的檔案(so檔案)複製到裡面。最後工程目錄的結構如下圖所示:


2.3 新增依賴

① 修改專案下的 build.gradle 檔案,在android節點下,定義 sourceSets 內容為 jniLibs 路徑,這樣才能使用so庫檔案;

② 同樣地,修改 build.gradle 檔案,新增Jar包的依賴項。

最後,新增好依賴以後檔案的部分內容(其他部分暫時用省略號替代)如下所示:

apply plugin: 'com.android.application'

android {
    ......
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}

dependencies {
    ......
    compile files('libs/pushservice-5.5.0.50.jar')
}

2.4 配置AndroidManifest檔案

2.4.1 新增相關許可權

在 manifest 節點下新增許可權:

<!-- Push service 執行需要的許可權 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 富媒體需要宣告的許可權 -->
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />

<!-- 適配Android N系統必需的ContentProvider寫許可權宣告,寫許可權包含*自己的應用包名*-->
<uses-permission
    android:name="baidu.push.permission.WRITE_PUSHINFOPROVIDER.com.project.baidupushtest" />
<permission
    android:name="baidu.push.permission.WRITE_PUSHINFOPROVIDER.com.project.baidupushtest"
    android:protectionLevel="normal">
</permission>

2.4.2 註冊Receiver 和 Service

在application節點中註冊廣播接收器和服務:

<!-- push service start -->
<!-- 用於接收系統訊息以保證PushService正常執行 -->
<receiver android:name="com.baidu.android.pushservice.PushServiceReceiver"
    android:process=":bdservice_v1" >
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
        <action android:name="com.baidu.android.pushservice.action.notification.SHOW" />
        <action android:name="com.baidu.android.pushservice.action.media.CLICK" />
        <!-- 以下四項為可選的action宣告,可大大提高service存活率和訊息到達速度 -->
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
        <action android:name="android.intent.action.USER_PRESENT" />
        <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
        <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
    </intent-filter>
</receiver>
<!-- Push服務接收客戶端傳送的各種請求-->
<receiver android:name="com.baidu.android.pushservice.RegistrationReceiver"
    android:process=":bdservice_v1" >
    <intent-filter>
        <action android:name="com.baidu.android.pushservice.action.METHOD" />
        <action android:name="com.baidu.android.pushservice.action.BIND_SYNC" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.PACKAGE_REMOVED" />
        <data android:scheme="package" />
    </intent-filter>
</receiver>
<service android:name="com.baidu.android.pushservice.PushService" 
    android:exported="true"
    android:process=":bdservice_v1" >
    <intent-filter >
        <action android:name="com.baidu.android.pushservice.action.PUSH_SERVICE" />
    </intent-filter>
</service>
<!-- 4.4版本新增的CommandService宣告,提升小米和魅族手機上的實際推送到達率 -->
<service android:name="com.baidu.android.pushservice.CommandService"
    android:exported="true" />

<!-- 適配Android N系統必需的ContentProvider宣告,寫許可權包含應用包名-->
<provider
    android:name="com.baidu.android.pushservice.PushInfoProvider"
    android:authorities="com.baidu.push.example.bdpush"
    android:writePermission="baidu.push.permission.WRITE_PUSHINFOPROVIDER.com.baidu.push.example"
    android:protectionLevel = "signature"
    android:exported="true" />
<!-- push結束 -->

2.5 自定義Receiver

我們需要通過繼承 com.baidu.android.pushservice.PushMessageReceiver 實現一個Receiver,並在 AndroidManifest 檔案中註冊,來接受推送的訊息,處理通知的點選事件等。

2.5.1 繼承實現Receiver類

由於只是簡單的接收一下推送訊息,所以只重寫了 onBind 方法。部分程式碼如下:

public class MyPushMessageReceiver extends PushMessageReceiver {
    @Override
    public void onBind(Context context, int errorCode, String s, String s1,
		String s2, String s3) {
        if (errorCode == 0) {
            Toast.makeText(context, "繫結成功!", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "繫結失敗!"+errorCode, Toast.LENGTH_SHORT).show();
        }

    }
    ......
}

2.5.2 註冊自定義Receiver

在application節點中註冊自定義的Receiver

<!-- 此處Receiver名字修改為當前包名路徑 -->
<receiver android:name=".MyPushMessageReceiver">
    <intent-filter>
        <!-- 接收push訊息 -->
        <action android:name="com.baidu.android.pushservice.action.MESSAGE" />
        <!-- 接收bind、setTags等method的返回結果-->
        <action android:name="com.baidu.android.pushservice.action.RECEIVE" />
        <!-- 接收通知點選事件,和通知自定義內容 -->
        <action android:name="com.baidu.android.pushservice.action.notification.CLICK" />
    </intent-filter>
</receiver>

2.6 啟動雲推送

① 在 MainActivity 中新增如下程式碼進行初始化,將最後一個引數替換成自己的API Key。

PushManager.startWork(getApplicationContext(), PushConstants.LOGIN_TYPE_API_KEY, 
        Constants.APIKEY_VALUE);

② 程式啟動的時候,顯示“繫結成功”則表示可以接收推送資訊了(之前在自定義Receiver類中寫的)。利用雲推送控制檯簡單測試一下,在應用列表頁面,點選建立推送 —> 建立通知 —> 填寫相關內容後傳送,能成功收到資訊,我們就已經成功了一大半了。


3. 通過客戶端請求推送通知
依照上兩節的內容,我們已經可以推送/接收通知了,但是推送還是要使用網頁控制檯才能完成。接下來,就讓我們來看看如何利用REST API實現客戶端推送。同樣地,也有相應的文件介紹了其具體的用法:http://push.baidu.com/doc/restapi/sdk_developer
3.1 請求的URL
百度提供了REST風格的htttp和https介面,如下所示:

http[s]://api.tuisong.baidu.com/rest/3.0/{class}/{method}?{query_string}
{class}是所要請求的資源類別,如我們想要推送通知,則為push;

{method}是具體的操作方法名稱,如要推送給安裝了我們程式的所有使用者,則為all;即:

http://api.tuisong.baidu.com/rest/3.0/push/all
{query_string}是具體的請求引數,有幾點要注意一下:
① 對於GET請求,{query_string}放在“?”後面傳遞;
② 對於POST請求,{query_string}放在POST引數中傳遞
③ {query_string}由通用引數和具體API呼叫引數2個部分組成;
④ {query_string}中的key/value對都必須經過urlencode處理,且必須是UTF-8編碼;
⑤ 目前查詢介面同時支援POST和GET方式,非查詢介面僅支援POST方式。

我們請求推送使用的是POST的方式。

3.2 請求的引數
可劃分為①公共請求引數:即每次請求都需要傳遞的引數;②API呼叫引數:每種請求型別都會有相對應的請求引數。
3.2.1 公共請求引數

通過下表更進一步瞭解一下通用引數:

引數名 型別 必需 描述
 apikey  string  是  應用的Api Key,用於標識應用。
 timestamp             number        是  使用者發起請求時的unix時間戳。本次請求籤名的有效時間為以該時間戳開始的10分鐘。
 sign  string  是  通過簽名演算法生成,與apikey成對出現。用於防止請求內容被篡改。下一小節詳細介紹。
 expires  number  否  使用者指定本次請求籤名的失效時間。格式為unix時間戳形式,用於防止 replay 型攻擊。為保證防止 replay攻擊演算法的正確有效,需保證客戶端系統時間正確。
 device_type number  否  當一個應用同時支援多個裝置平臺型別(比如:Android和iOS),請務必設定該引數。其餘情況可不設定。Android對應的引數值為3,IOS為4.

3.2.2 API呼叫引數

引數名 型別 必需 限制 描述
 msg_type  number  否  取值如下:0:訊息;1:通知。預設為0  訊息型別
 msg  string  是  訊息/通知資料格式詳見下方介紹  訊息內容,json格式
 msg_expires  number  否  0~604800(86400*7),預設為5小時(18000秒)  訊息過期時間,單位為秒
 deploy_status     number    否  取值為:1:開發狀態;2:生產狀態; 若不指定,則預設設定為生產狀態。  設定iOS應用的部署狀態,僅iOS應用推送時使用
 send_time  number   否  指定的實際傳送時間,必須在當前時間60s以外,1年以內  定時推送,用於指定的實際傳送時間
② Android平臺的訊息無格式要求,開發者可以自定義。詳情可見:http://push.baidu.com/doc/restapi/msg_struct
{  
    "title" : "hello" ,  
    "description": "hello world" //必選  
    "notification_builder_id": 0, //可選  
    "notification_basic_style": 7, //可選  
    "open_type":0, //可選  
    "url": "http://developer.baidu.com", //可選  
    "pkg_content":"", //可選  
    "custom_content":{"key":"value"},  
}
程式碼就比較隨意了,直接貼出來:
String msg = String.format("{" + Constants.TITLE + ":\"%s\"," 
                + Constants.DESCRIPTION + ":\"%s\"," 
                + Constants.NOTIFICATION_BUILDER_ID + ":0," 
                + Constants.NOTIFICATION_BASIC_STYLE + ":7}", title, contents);
requestData.put(Constants.MSG, msg);
拼接好之後放入請求Map中,用於後面的傳遞。

3.3 簽名演算法

雲推送API使用的簽名演算法(http://push.baidu.com/doc/restapi/sdk_developer#簽名演算法)如下:
① 獲取請求的http method;
② 獲取請求的url,包括host和sheme,但不包括query_string的部分;
③ 將所有引數(包括GET或POST的引數,但不包含簽名欄位)格式化為“key=value”格式,如“k1=v1”、“k2=v2”、“k3=v3”;
④ 將格式化好的引數鍵值對以字典序升序排列後,拼接在一起,如“k1:v1,k2 :v2,k3:v3”,並將http method和url按順序拼接在這個字串前面;
⑤ 在拼接好的字串末尾追加上應用的secret_key,並進行urlencode,形成base_string;

瞭解簽名演算法的規則後,讓我們來看看程式碼是怎麼寫的:

/**
     * 通過簽名演算法得到sign值
     *
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    private StringBuilder getSign(RequestData data) 
            throws NoSuchAlgorithmException, UnsupportedEncodingException {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(Constants.POST);
        stringBuilder.append(Constants.REQUEST_URL);
        for (Map.Entry<String, String> i : data.entrySet())
        {
            stringBuilder.append(i.getKey());
            stringBuilder.append('=');
            stringBuilder.append(i.getValue());
        }
        stringBuilder.append(Constants.SECRETKEY_VALUE);

        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        messageDigest.reset();

        Log.e("TAG", stringBuilder.toString());
        messageDigest.update(urlEncode(stringBuilder.toString()).getBytes());
        byte[] md5 = messageDigest.digest();

        stringBuilder.setLength(0);
        for (byte b : md5) {
            stringBuilder.append(String.format("%02x", b & 0xff));
        }

        return stringBuilder;
    }

    /**
     * url編碼方式
     *
     * @param str
     *            指定編碼方式,預設為utf-8
     * @return
     * @throws UnsupportedEncodingException
     */
    private String urlEncode(String str) throws UnsupportedEncodingException {
        String rc = URLEncoder.encode(str, "utf-8");
        return rc.replace("*", "%2A");
    }
最後請求的時候,記得要設定請求Header中的Content-Type和User-Agent的值。大致的內容就這些,大家可以去雲推送官網檢視詳細的API文件,完整的程式碼可以通過下方的連結下載。如若文中或程式碼有任何寫的不對或者不好的地方,歡迎大家指出。

程式原始碼: