極光推送之Android客戶端使用指南--基礎(chǔ)篇

本文中涉及到的所有代碼現(xiàn)已在Github上開源,地址: https://github.com/xuexiangjys/JPushSample

前言

極光推送是國內(nèi)最早做第三方消息推送平臺(tái)的公司桨啃,在消息推送界還是相對有影響力的。我最早是在2016年接觸到極光消息推送的清寇,那時(shí)候公司需要做消息推送業(yè)務(wù)吁恍,但是由于之前沒做過消息推送倍阐,且自建消息推送平臺(tái)代價(jià)太高,而且穩(wěn)不穩(wěn)定誰也不敢打包票,于是就選擇了當(dāng)時(shí)較為有名的極光推送斜筐。

那么當(dāng)時(shí)我為什么選擇極光推送呢冰啃?

  • 1.免費(fèi)邓夕。免費(fèi)版本的每個(gè) Appkey 的最高推送頻率為 600 次/分鐘,而且沒有推送數(shù)量限制阎毅,者對于消息推送業(yè)務(wù)剛起步的企業(yè)來說焚刚,完全夠用了。

  • 2.上手簡單扇调,文檔齊全矿咕。平臺(tái)官網(wǎng)上的文檔非常詳細(xì),下載下來的演示demo也非常豐富狼钮,通過簡單的幾行代碼就可以輕松接入碳柱。

  • 3.功能豐富。比起小米推送熬芜、華為推送莲镣、信鴿推送、友盟推送來說涎拉,極光推送的功能是最全的瑞侮。想具體了解這幾種推送的可參見我的開源框架XPush.

  • 4.社區(qū)支持度高。就拿我們Android來說鼓拧,不僅支持原生集成半火,還支持React Native、Flutter季俩、Weex慈缔、HBuilder、Cordova等混合開發(fā)方式种玛。

那么極光推送真的有那么好嗎藐鹤?其實(shí)也不全是,我在使用的過程中也發(fā)現(xiàn)了一些問題:

  • 1.推送的到達(dá)率差一點(diǎn)赂韵。只要應(yīng)用退到后臺(tái)被系統(tǒng)回收或者被用戶殺死娱节,基本就很難再收到推送了。這點(diǎn)自然比不上那些手機(jī)廠商的推送祭示。

  • 2.沒有免費(fèi)開放廠商通道推送集成肄满。想要集成廠商通道推送的話,還需要充錢成為VIP才行。

不過如果你是消息推送的初學(xué)者的話稠歉,我想極光推送肯定是你不二的選擇掰担。那么下面來跟著我學(xué)習(xí)如何使用極光推送吧!


快速集成指南

本文是基于jpush:3.5.4jcore:2.2.6版本介紹的怒炸,暫只介紹最新推薦的使用方法带饱,那些過時(shí)的用法這里我就不多介紹了,想了解的可以去極光推送官方文檔查看阅羹。

集成前的準(zhǔn)備工作

在接入極光推送前勺疼,首先需要獲取到應(yīng)用的AppKey,它是應(yīng)用的唯一標(biāo)識(shí)捏鱼。

1.創(chuàng)建極光推送開發(fā)者帳號(hào)

要?jiǎng)?chuàng)建極光推送開發(fā)者帳號(hào)执庐,請?jiān)L問極光推送官方網(wǎng)站: https://www.jiguang.cn/push

創(chuàng)建賬號(hào)

2.創(chuàng)建應(yīng)用

進(jìn)入極光控制臺(tái)后,點(diǎn)擊“創(chuàng)建應(yīng)用”按鈕导梆,填寫應(yīng)用名稱即可創(chuàng)建應(yīng)用成功轨淌。同時(shí)點(diǎn)擊“推送設(shè)置”,在 Android 版塊填上你的應(yīng)用包名看尼,選擇保存即可猿诸。

創(chuàng)建應(yīng)用
設(shè)置包名

3.獲取應(yīng)用的AppKey

在極光控制臺(tái)點(diǎn)擊"應(yīng)用設(shè)置"中的"應(yīng)用信息",獲取應(yīng)用的AppKey狡忙。

獲取AppKey

引入依賴庫

方法一 jcenter自動(dòng)集成

使用 jcenter 自動(dòng)集成的開發(fā)者梳虽,不需要在項(xiàng)目中添加 jar 和 so,jcenter 會(huì)自動(dòng)完成依賴灾茁;在 AndroidManifest.xml 中不需要添加任何 JPush SDK 相關(guān)的配置窜觉,jcenter 會(huì)自動(dòng)導(dǎo)入。

1.配置項(xiàng)目的build.gradle文件

android {

    defaultConfig {
        applicationId "com.xxx.xxx" //JPush平臺(tái)上注冊的應(yīng)用包名.

        ...

        ndk {
            //選擇要添加的對應(yīng) cpu 類型的 .so 庫北专。
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
            //,'x86', 'x86_64', 'mips', 'mips64'
        }
        manifestPlaceholders = [
                JPUSH_PKGNAME: defaultConfig.applicationId,
                JPUSH_APPKEY : "你的 Appkey ",//值來自開發(fā)者平臺(tái)取得的AppKey
                JPUSH_CHANNEL: "default_developer",
        ]
    }

}

dependencies {
    ...
    //引入JPush依賴庫
    implementation 'cn.jiguang.sdk:jpush:3.5.4'
    implementation 'cn.jiguang.sdk:jcore:2.2.6'
}

2.配置項(xiàng)目的AndroidManifest.xml文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xuexiang.jpush">

    <application>

        <!-- 1.這個(gè)是自定義Service禀挫,要繼承極光JCommonService,可以在更多手機(jī)平臺(tái)上使得推送通道保持的更穩(wěn)定 -->
        <service
            android:name=".PushService"
            android:enabled="true"
            android:exported="false"
            android:process=":pushcore">
            <intent-filter>
                <action android:name="cn.jiguang.user.service.action" />
            </intent-filter>
        </service>

        <!-- 2.用戶自定義接收消息器,所有你想要知道的消息都在這里-->
        <receiver android:name=".core.push.PushMessageReceiver">
            <intent-filter>
                <action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </receiver>

    </application>

</manifest>

點(diǎn)擊參見自動(dòng)集成的項(xiàng)目源碼

方法二 本地手動(dòng)集成

1.首先你需要先去下載SDK拓颓,下載地址: https://docs.jiguang.cn/jpush/resources/

2.解壓SDK语婴,將壓縮包下的libs內(nèi)容復(fù)制到項(xiàng)目的libs下

3.配置項(xiàng)目的build.gradle文件

android {

    defaultConfig {
        applicationId "com.xxx.xxx" //JPush平臺(tái)上注冊的應(yīng)用包名.

        ...

        ndk {
            //選擇要添加的對應(yīng) cpu 類型的 .so 庫。
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
            //,'x86', 'x86_64', 'mips', 'mips64'
        }
        manifestPlaceholders = [
                JPUSH_PKGNAME: defaultConfig.applicationId,
                JPUSH_APPKEY : "你的 Appkey ",//值來自開發(fā)者平臺(tái)取得的AppKey
                JPUSH_CHANNEL: "default_developer",
        ]
    }

    sourceSets {
        //設(shè)置libs目錄為so包的加載目錄
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

}

4.配置項(xiàng)目的AndroidManifest.xml文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.xxx.xxx">

    <permission
        android:name="${applicationId}.permission.JPUSH_MESSAGE"
        android:protectionLevel="signature" />

    <!-- Required  一些系統(tǒng)要求的權(quán)限驶睦,如訪問網(wǎng)絡(luò)等-->
    <uses-permission android:name="${applicationId}.permission.JPUSH_MESSAGE" />
    <uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <!-- 用于開啟 debug 版本的應(yīng)用在6.0 系統(tǒng)上 層疊窗口權(quán)限 -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <!-- Optional for location -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.GET_TASKS" />

    <application>
        <!-- Required SDK核心功能-->
        <activity
            android:name="cn.jpush.android.ui.PushActivity"
            android:configChanges="orientation|keyboardHidden"
            android:exported="false"
            android:theme="@android:style/Theme.NoTitleBar">
            <intent-filter>
                <action android:name="cn.jpush.android.ui.PushActivity" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </activity>
        <!-- Required SDK 核心功能-->
        <!-- 可配置android:process參數(shù)將PushService放在其他進(jìn)程中 -->
        <service
            android:name="cn.jpush.android.service.PushService"
            android:exported="false"
            android:process=":pushcore">
            <intent-filter>
                <action android:name="cn.jpush.android.intent.REGISTER" />
                <action android:name="cn.jpush.android.intent.REPORT" />
                <action android:name="cn.jpush.android.intent.PushService" />
                <action android:name="cn.jpush.android.intent.PUSH_TIME" />
            </intent-filter>
        </service>
        <!-- since 3.0.9 Required SDK 核心功能-->
        <provider
            android:name="cn.jpush.android.service.DataProvider"
            android:authorities="${applicationId}.DataProvider"
            android:exported="false" />
        <!-- since 1.8.0 option 可選項(xiàng)砰左。用于同一設(shè)備中不同應(yīng)用的JPush服務(wù)相互拉起的功能。 -->
        <!-- 若不啟用該功能可刪除該組件场航,將不拉起其他應(yīng)用也不能被其他應(yīng)用拉起 -->
        <service
            android:name="cn.jpush.android.service.DaemonService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="cn.jpush.android.intent.DaemonService" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </service>
        <!-- since 3.1.0 Required SDK 核心功能-->
        <provider
            android:name="cn.jpush.android.service.DownloadProvider"
            android:authorities="${applicationId}.DownloadProvider"
            android:exported="true" />
        <!-- Required SDK核心功能-->
        <receiver
            android:name="cn.jpush.android.service.PushReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter android:priority="1000">
                <!--Required  顯示通知欄 -->
                <action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY" />
                <category android:name="${applicationId}" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.USER_PRESENT" />
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
            <!-- Optional -->
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />

                <data android:scheme="package" />
            </intent-filter>
        </receiver>

        <!-- Required SDK核心功能-->
        <receiver
            android:name="cn.jpush.android.service.AlarmReceiver"
            android:exported="false" />

        <!--since 3.3.0 Required SDK核心功能-->
        <activity
            android:name="cn.jpush.android.service.JNotifyActivity"
            android:exported="true"
            android:taskAffinity="jpush.custom"
            android:theme="@android:style/Theme.Translucent.NoTitleBar">
            <intent-filter>
                <action android:name="cn.jpush.android.intent.JNotifyActivity" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </activity>


        <!-- *********************下面這兩個(gè)是需要你自己定義的**************************** -->


        <!-- since 3.3.0 Required SDK 核心功能-->
        <!-- 1.這個(gè)是自定義Service缠导,要繼承極光JCommonService,可以在更多手機(jī)平臺(tái)上使得推送通道保持的更穩(wěn)定 -->
        <service
            android:name=".PushService"
            android:enabled="true"
            android:exported="false"
            android:process=":pushcore">
            <intent-filter>
                <action android:name="cn.jiguang.user.service.action" />
            </intent-filter>
        </service>

        <!-- 2.用戶自定義接收消息器,所有你想要知道的消息都在這里-->
        <receiver android:name=".core.push.PushMessageReceiver">
            <intent-filter>
                <action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </receiver>


        <meta-data
            android:name="JPUSH_CHANNEL"
            android:value="${JPUSH_CHANNEL}" />
        <!-- 值來自開發(fā)者平臺(tái)取得的AppKey-->
        <meta-data
            android:name="JPUSH_APPKEY"
            android:value="${JPUSH_APPKEY}" />

    </application>

</manifest>

點(diǎn)擊參見手動(dòng)集成的項(xiàng)目源碼

初始化

1.在Application中初始化JPush

public class MyApp extends Application {
@Override
    public void onCreate() {
        super.onCreate();
        initJPush();
    }
}

/**
 * 初始化極光推送
 */
private void initJPush() {
    JPushInterface.setDebugMode(BuildConfig.DEBUG);
    //只需要在應(yīng)用程序啟動(dòng)時(shí)調(diào)用一次該 API 即可
    JPushInterface.init(this);
}

2.在應(yīng)用的第一個(gè)頁面申請權(quán)限(可選)

由于Android手機(jī)定制ROM太多溉痢,部分手機(jī)的通知欄權(quán)限默認(rèn)是關(guān)閉的僻造,需要用戶手動(dòng)打開憋他。如果不打開通知欄權(quán)限的話,即使你連上了推送髓削,也無法收到推送消息竹挡。

/**
 * 申請定位、存儲(chǔ)和通知欄的權(quán)限
 *
 * @param activity
 */
public static void requestPermission(Activity activity) {
    //打開通知欄的權(quán)限
    if (JPushInterface.isNotificationEnabled(activity) == 0) {
        new AlertDialog.Builder(activity)
                .setCancelable(false)
                .setMessage("通知權(quán)限未打開立膛,是否前去打開揪罕?")
                .setPositiveButton("是", (d, w) -> JPushInterface.goToAppNotificationSettings(activity))
                .setNegativeButton("否", null)
                .show();
    }
    //申請定位、存儲(chǔ)權(quán)限
    JPushInterface.requestPermission(activity);
}

運(yùn)行調(diào)試

當(dāng)完成以上步驟后旧巾,可直接運(yùn)行程序耸序,并查看logcat日志忍些,設(shè)置過濾條件為"JIGUANG"鲁猩,如果出現(xiàn)"Register succeed"和"registrationId:xxxxxxxxxxxxxx"字樣,即為集成成功罢坝!如下圖所示:

運(yùn)行調(diào)試

注意事項(xiàng):

  • 一定要保證配置的AppKey和應(yīng)用的包名保持一致廓握。
  • 一定要保證運(yùn)行的設(shè)備網(wǎng)絡(luò)是可用的,否則無法連接推送嘁酿。

混淆配置

配置項(xiàng)目的proguard-rules.pro文件隙券。

-dontoptimize
-dontpreverify
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
-dontwarn cn.jiguang.**
-keep class cn.jiguang.** { *; }
-keep class cn.jiguang.** { *; }
-keep class * extends cn.jpush.android.service.JPushMessageReceiver{*;}

基礎(chǔ)功能使用

初始化

1.上面已經(jīng)講過了,推送初始化建議在自定義的 Application 中的 onCreate 中調(diào)用闹司,且推送初始化只需要調(diào)用一次即可娱仔。

JPushInterface.init(Context context);

2.推送初始化成功后,平臺(tái)會(huì)返回一個(gè)唯一的token令牌游桩,那就是RegistrationID牲迫,獲取它的方法如下:

JPushInterface.getRegistrationID(Context context);

3.獲取當(dāng)前推送的連接狀態(tài)方法如下:

JPushInterface.getConnectionState(Context context)

點(diǎn)擊參見推送初始化演示源碼

推送狀態(tài)控制

1.停止推送。在某些業(yè)務(wù)中借卧,我們需要臨時(shí)暫停推送盹憎,例如賬戶退出登陸等,這個(gè)時(shí)候我們可以調(diào)用如下方法:

JPushInterface.stopPush(Context context);

需要注意的是铐刘,這里的停止推送只是個(gè)本地客戶端的操作陪每,并不會(huì)通知到推送服務(wù)平臺(tái)。其表現(xiàn)效果類似設(shè)備斷網(wǎng)镰吵,將不會(huì)收到任何推送消息檩禾,并且極光推送所有的其他 API 調(diào)用都無效,除了resumePush恢復(fù)推送服務(wù)的方法疤祭。

2.恢復(fù)推送锌订。當(dāng)調(diào)用了停止推送的方法后,只有調(diào)用恢復(fù)推送的方法后画株,極光推送服務(wù)才能正常工作辆飘。方法如下:

JPushInterface.resumePush(Context context);

3.獲取推送的工作狀態(tài)啦辐。想要知道當(dāng)前推送服務(wù)是否正在工作,可通過如下方法:

JPushInterface.isPushStopped(Context context);

點(diǎn)擊參見推送狀態(tài)控制演示源碼


操作別名alias

別名在極光推送中尤為重要蜈项,通常我們用得最多的就是根據(jù)別名進(jìn)行推送芹关。我們通常的做法是用戶登陸后,業(yè)務(wù)平臺(tái)會(huì)返回一個(gè)平臺(tái)生成的唯一識(shí)別號(hào)作為推送的別名紧卒,然后后臺(tái)需要推送的時(shí)候侥衬,就直接拿著這個(gè)別名通知極光推送服務(wù)進(jìn)行消息推送。

1.綁定別名alias跑芳。

JPushInterface.setAlias(Context context, int sequence, String alias);

2.解綁別名alias轴总。

JPushInterface.deleteAlias(Context context, int sequence);

3.獲取綁定的別名alias。

JPushInterface.getAlias(Context context, int sequence);

注意事項(xiàng):

1.這里的sequence主要就是操作識(shí)別碼博个,用于識(shí)別操作類型怀樟,由使用者自己定義。

2.以上所有的方法返回的都是void(都是異步操作)盆佣,方法的返回都在自定義的消息接收器中往堡,就是上面繼承JPushMessageReceiver由使用者自定義的廣播接收器中獲取。

3.別名相關(guān)操作的結(jié)果都在JPushMessageReceiveronAliasOperatorResult方法中回調(diào)共耍,需要獲取別名操作結(jié)果的可重寫該方法虑灰。

點(diǎn)擊參見別名操作演示源碼

操作標(biāo)簽Tags

標(biāo)簽好比一個(gè)分組,當(dāng)我們需要對某一類特殊群體進(jìn)行消息推送時(shí)痹兜,便可使用標(biāo)簽進(jìn)行推送穆咐。

1.增加標(biāo)簽Tags。這是一個(gè)增量請求字旭。

JPushInterface.addTags(Context context, int sequence, Set<String> tags);

2.刪除標(biāo)簽Tags对湃。

JPushInterface.deleteTags(Context context, int sequence, Set<String> tags);

3.獲取標(biāo)簽Tags。

JPushInterface.getAllTags(Context context, int sequence);

4.設(shè)置標(biāo)簽Tags谐算。這是一個(gè)全量請求熟尉,會(huì)覆蓋之前設(shè)置的標(biāo)簽。

JPushInterface.setTags(Context context, int sequence, Set<String> tags);

5.清除所有標(biāo)簽洲脂。

JPushInterface.cleanTags(Context context, int sequence);

6.查詢指定 tag 與當(dāng)前用戶綁定的狀態(tài)斤儿。

JPushInterface.checkTagBindState(Context context, int sequence, String tag);

注意事項(xiàng):

1.這里的sequence和別名方法中的一樣,也是操作識(shí)別碼恐锦,用于識(shí)別操作類型往果,由使用者自己定義。

2.以上所有的方法返回的都是void(都是異步操作)一铅,方法的返回都在自定義的消息接收器中陕贮,就是上面繼承JPushMessageReceiver由使用者自定義的廣播接收器中獲取。

3.標(biāo)簽相關(guān)操作的結(jié)果都在JPushMessageReceiveronTagOperatorResult方法中回調(diào)潘飘,需要獲取標(biāo)簽操作結(jié)果的可重寫該方法肮之。

4.checkTagBindState方法的結(jié)果是在JPushMessageReceiveronCheckTagOperatorResult方法中回調(diào)掉缺,需要獲取標(biāo)簽查詢匹配結(jié)果的可重寫該方法。

點(diǎn)擊參見標(biāo)簽操作演示源碼


操作結(jié)果獲取

這里的操作主要包括:注冊戈擒、別名(綁定眶明、解綁、獲取)筐高、標(biāo)簽(添加搜囱、刪除、獲取柑土、設(shè)置蜀肘、清除、狀態(tài)檢查)稽屏、手機(jī)號(hào)設(shè)置等扮宠。由于極光提供的這些操作都是異步的,且方法不能直接返回結(jié)果和提供回調(diào)接口诫欠,因此只能通過重寫JPushMessageReceiver中相應(yīng)的方法獲取涵卵。

所有的操作結(jié)果都可以從JPushMessageReceiver提供的回調(diào)方法中獲取浴栽。但是JPushMessageReceiver最多只能作為消息的中轉(zhuǎn)站荒叼,使用起來極為不便,因此我們可以結(jié)合一些事件機(jī)制來處理典鸡,將這些結(jié)果包裝為一個(gè)個(gè)推送事件向外發(fā)出去被廓,這樣只需要在需要的地方訂閱一下事件就可以獲取到結(jié)果了塘匣。下面我以RxBus為例簡單編寫赌朋,使用的庫是我的開源庫RxUtil2

1.定義操作事件的類型,用于識(shí)別操作類型。上文中提到的sequence參數(shù)就可以使用它扭吁。

/**
 * 推送事件的類型
 */
@IntDef({TYPE_REGISTER, TYPE_UNREGISTER, TYPE_CONNECT_STATUS_CHANGED, TYPE_ADD_TAGS, TYPE_DEL_TAGS, TYPE_GET_TAGS, TYPE_BIND_ALIAS, TYPE_UNBIND_ALIAS, TYPE_GET_ALIAS})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {
    /**
     * 注冊推送
     */
    int TYPE_REGISTER = 2000;
    /**
     * 取消注冊推送
     */
    int TYPE_UNREGISTER = 2001;
    /**
     * 推送連接狀態(tài)發(fā)生變化
     */
    int TYPE_CONNECT_STATUS_CHANGED = 2002;

    /**
     * 綁定別名
     */
    int TYPE_BIND_ALIAS = 2010;
    /**
     * 解綁別名
     */
    int TYPE_UNBIND_ALIAS = 2011;
    /**
     * 獲取別名
     */
    int TYPE_GET_ALIAS = 2012;

    /**
     * 添加標(biāo)簽[增量]
     */
    int TYPE_ADD_TAGS = 2020;
    /**
     * 刪除標(biāo)簽
     */
    int TYPE_DEL_TAGS = 2021;
    /**
     * 獲取標(biāo)簽
     */
    int TYPE_GET_TAGS = 2022;
    /**
     * 設(shè)置標(biāo)簽[全量]
     */
    int TYPE_SET_TAGS = 2023;
    /**
     * 清除所有標(biāo)簽
     */
    int TYPE_CLEAN_TAGS = 2024;
    /**
     * 查詢指定 tag 與當(dāng)前用戶綁定的狀態(tài)
     */
    int TYPE_CHECK_TAG_BIND_STATE = 2025;
}

2.定義推送事件的載體.

該載體只需要定義三個(gè)成員變量:mType(事件類型)球碉、mIsSuccess(是否成功)蜓斧、mData(攜帶的數(shù)據(jù))。如下所示:

/**
 * 推送事件的載體
 */
public final class PushEvent {
    public static final String KEY_PUSH_EVENT = "com.xuexiang.jpushsample.core.push.event.KEY_PUSH_EVENT";
    /**
     * 事件類型
     */
    private int mType;
    /**
     * 是否成功(也可以定義為int型的結(jié)果碼)
     */
    private boolean mIsSuccess;
    /**
     * 攜帶的數(shù)據(jù)(也可以定義為String型的數(shù)據(jù))
     */
    private Object mData;

    public PushEvent(@EventType int type) {
        mType = type;
    }

    public PushEvent(@EventType int type, boolean isSuccess) {
        mType = type;
        mIsSuccess = isSuccess;
    }

    public PushEvent(@EventType int type, Object data) {
        mType = type;
        mData = data;
    }

    public int getType() {
        return mType;
    }

    public PushEvent setType(@EventType int type) {
        mType = type;
        return this;
    }

    public boolean isSuccess() {
        return mIsSuccess;
    }

    public PushEvent setSuccess(boolean success) {
        mIsSuccess = success;
        return this;
    }

    public Object getData() {
        return mData;
    }

    public PushEvent setData(Object data) {
        mData = data;
        return this;
    }
}

3.事件處理并發(fā)送.

JPushMessageReceiver中重寫指定的方法睁冬,并將結(jié)果轉(zhuǎn)譯為一個(gè)個(gè)PushEvent發(fā)送出去挎春。

/**
 * 極光推送消息接收器
 */
public class PushMessageReceiver extends JPushMessageReceiver {
    private static final String TAG = "JPush-Receiver";
    //======================下面的都是操作的回調(diào)=========================================//

    @Override
    public void onRegister(Context context, String registrationId) {
        Log.e(TAG, "[onRegister]:" + registrationId);
        RxBusUtils.get().post(KEY_PUSH_EVENT, new PushEvent(EventType.TYPE_REGISTER, true, registrationId));
    }

    /**
     * 連接狀態(tài)發(fā)生變化
     *
     * @param context
     * @param isConnected 是否已連接
     */
    @Override
    public void onConnected(Context context, boolean isConnected) {
        Log.e(TAG, "[onConnected]:" + isConnected);
        RxBusUtils.get().post(KEY_PUSH_EVENT, new PushEvent(EventType.TYPE_CONNECT_STATUS_CHANGED, isConnected));
    }

    /**
     * 所有和標(biāo)簽相關(guān)操作結(jié)果
     *
     * @param context
     * @param jPushMessage
     */
    @Override
    public void onTagOperatorResult(Context context, JPushMessage jPushMessage) {
        Log.e(TAG, "[onTagOperatorResult]:" + jPushMessage);
        PushEvent pushEvent = new PushEvent(jPushMessage.getSequence(), jPushMessage.getErrorCode() == 0)
                .setData(JPushInterface.getStringTags(jPushMessage.getTags()));
        RxBusUtils.get().post(KEY_PUSH_EVENT, pushEvent);
    }

    /**
     * 所有和別名相關(guān)操作結(jié)果
     *
     * @param context
     * @param jPushMessage
     */
    @Override
    public void onAliasOperatorResult(Context context, JPushMessage jPushMessage) {
        Log.e(TAG, "[onAliasOperatorResult]:" + jPushMessage);
        PushEvent pushEvent = new PushEvent(jPushMessage.getSequence(), jPushMessage.getErrorCode() == 0)
                .setData(jPushMessage.getAlias());
        RxBusUtils.get().post(KEY_PUSH_EVENT, pushEvent);
    }

    /**
     * 標(biāo)簽狀態(tài)檢測結(jié)果
     *
     * @param context
     * @param jPushMessage
     */
    @Override
    public void onCheckTagOperatorResult(Context context, JPushMessage jPushMessage) {
        Log.e(TAG, "[onCheckTagOperatorResult]:" + jPushMessage);
        PushEvent pushEvent = new PushEvent(jPushMessage.getSequence(), jPushMessage.getErrorCode() == 0)
                .setData(jPushMessage);
        RxBusUtils.get().post(KEY_PUSH_EVENT, pushEvent);
    }
}

4.在需要獲取結(jié)果的地方訂閱或者取消事件。

    @Override
    protected void initListeners() {
        //訂閱推送事件
        mPushEvent = RxBusUtils.get().onMainThread(KEY_PUSH_EVENT, PushEvent.class, this::handlePushEvent);
    }
    
   /**
     * 處理推送事件豆拨,獲取操作的結(jié)果
     * @param pushEvent
     */
    private void handlePushEvent(PushEvent pushEvent) {
        String content = pushEvent.getData().toString();
        switch (pushEvent.getType()) {
            case TYPE_BIND_ALIAS:
                if (pushEvent.isSuccess()) {
                    tvAlias.setText(content);
                    XToastUtils.success("別名[" + content + "]綁定成功");
                } else {
                    XToastUtils.error("別名[" + content + "]綁定失敗");
                }
                break;
            case TYPE_UNBIND_ALIAS:
                //別名解綁
                break;
            case TYPE_GET_ALIAS:
                //獲取別名
                break;
            case TYPE_ADD_TAGS:
                //添加標(biāo)簽
                break;
            case TYPE_DEL_TAGS:
                //刪除標(biāo)簽
                break;
            case TYPE_GET_TAGS:
                //獲取標(biāo)簽
                break;
            case TYPE_SET_TAGS:
                //設(shè)置標(biāo)簽
                break;
            case TYPE_CLEAN_TAGS:
                //清除標(biāo)簽
                break;
            case TYPE_CHECK_TAG_BIND_STATE:
                //檢查標(biāo)簽
                break;
                ........
            default:
                break;
        }
    }
    
    @Override
    public void onDestroyView() {
        if (mPushEvent != null) {
            //取消訂閱推送事件
            RxBusUtils.get().unregister(KEY_PUSH_EVENT, mPushEvent);
            mPushEvent = null;
        }
        super.onDestroyView();
    }

點(diǎn)擊參見操作結(jié)果獲取演示源碼

點(diǎn)擊參見自定義JPushMessageReceiver的源碼


消息接收

自定義消息

自定義消息直奋,又稱之為透傳消息。顧名思義是由使用者自己定義一套解析格式的消息施禾,這種消息在接收到后不會(huì)有任何界面上的展示脚线,攜帶內(nèi)容為String型,通常的做法是傳一個(gè)json弥搞。這種比較靈活的消息推送方式是最常用的一種邮绿。但是這里需要注意的是渠旁,這種消息是一種應(yīng)用內(nèi)的消息,一旦應(yīng)用被殺死船逮,將無法及時(shí)收到該消息一死。

1.自定義消息體(CustomMessage)介紹

字段名 類型 字段說明
messageId String 消息ID,對應(yīng)推送平臺(tái)上的消息唯一號(hào)
message String 對應(yīng)推送消息界面上的“自定義消息內(nèi)容”字段
extra String 保存服務(wù)器推送下來的附加字段傻唾。這是個(gè) JSON 字符串投慈,對應(yīng)推送消息界面上的“可選設(shè)置”里的附加字段。
title String 消息的標(biāo)題(沒多大作用)

2.自定義消息接收

如果想要接收自定義消息冠骄,只需重寫JPushMessageReceiver中的onMessage方法即可伪煤。在onMessage方法中將會(huì)回調(diào)CustomMessage自定義消息體。

普通通知消息

普通通知消息凛辣,就是在系統(tǒng)通知欄上顯示的消息抱既。但是如果通知的內(nèi)容為空,則不會(huì)在通知欄上展示通知扁誓。

1.通知消息體(NotificationMessage)介紹

字段名 類型 字段說明
messageId String 消息ID防泵,對應(yīng)推送平臺(tái)上的消息唯一號(hào)
notificationId int 通知欄的 Notification ID,用于清除 Notification
notificationTitle String 通知的標(biāo)題蝗敢,對應(yīng)推送通知界面上的“通知標(biāo)題”字段
notificationContent String 通知的內(nèi)容捷泞,對應(yīng)推送通知界面上的“通知內(nèi)容”字段
notificationExtras String 附加字段,對應(yīng)推送通知界面上的“可選設(shè)置”里的附加字段
notificationTitle String 通知的標(biāo)題寿谴,對應(yīng)推送通知界面上的“通知標(biāo)題”字段

2.普通通知消息接收

如果想要接收自定義消息锁右,只需重寫JPushMessageReceiver中的onNotifyMessageArrived方法即可。在onNotifyMessageArrived方法中將會(huì)回調(diào)NotificationMessage通知消息體讶泰。

3.通知消息被點(diǎn)擊

在做消息推送開發(fā)的時(shí)候咏瑟,我們一定會(huì)有一個(gè)需求:希望用戶點(diǎn)擊通知后,能夠自動(dòng)跳轉(zhuǎn)到我們應(yīng)用的某個(gè)頁面痪署。這個(gè)頁面可能是某一個(gè)活動(dòng)宣傳頁面码泞,也有可能是某個(gè)新聞或者視頻頁面。這個(gè)時(shí)候狼犯,我們就需要對通知消息點(diǎn)擊后的動(dòng)作進(jìn)行自定義余寥。

那么我們該如何自定義通知消息被點(diǎn)擊后的動(dòng)作呢?很簡單辜王,我們只需要重寫JPushMessageReceiver中的onNotifyMessageOpened方法劈狐,在方法中讀取傳遞過來的參數(shù),然后結(jié)合頁面路由機(jī)制(例如:ARouter)直接跳轉(zhuǎn)至指定頁面即可呐馆。

下面我將通過兩種不同的途徑來實(shí)現(xiàn) 點(diǎn)擊通知消息后跳轉(zhuǎn)至某一特定界面:

1.重寫JPushMessageReceiver中的onNotifyMessageOpened方法肥缔。

public class PushMessageReceiver extends JPushMessageReceiver {
    /**
     * 點(diǎn)擊通知回調(diào)
     *
     * @param context
     * @param message 通知消息
     */
    @Override
    public void onNotifyMessageOpened(Context context, NotificationMessage message) {
        Log.e(TAG, "[onNotifyMessageOpened]:" + message);
        //自定義打開到通知欄點(diǎn)擊后的容器頁
        Intent intent = parseNotificationMessage(IntentUtils.getIntent(context, NotificationTransferActivity.class, null, true), message);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        ActivityUtils.startActivity(intent);
    }

    /**
     * 解析極光通知消息:NotificationMessage
     */
    public static Intent parseNotificationMessage(@NonNull Intent intent, NotificationMessage message) {
        //這只是一個(gè)例子,暫時(shí)把跳轉(zhuǎn)的目標(biāo)頁設(shè)為 "通知信息展示"
        intent.putExtra("pageName", "通知信息展示");
        //通知標(biāo)題
        intent.putExtra("title", message.notificationTitle);
        //通知內(nèi)容
        intent.putExtra("content", message.notificationContent);
        //通知附帶拓展內(nèi)容
        intent.putExtra("extraMsg", message.notificationExtras);
        //通知附帶鍵值對
        intent.putExtra("keyValue", message.notificationExtras);
        return intent;
    }
}

2.通過DeepLink技術(shù)和通知欄中可選設(shè)置的自定義(打開指定頁面)相結(jié)合的方法汹来。

(1)首先需要在AndroidManifest.xml中定義deeplink攔截续膳。

<!--通知被點(diǎn)擊之后跳轉(zhuǎn)的頁面-->
<activity android:name=".activity.NotificationTransferActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data
            android:host="com.xuexiang.jpush"
            android:path="/notification"
            android:scheme="jpush" />
    </intent-filter>
</activity>

(2)在容器界面NotificationTransferActivity中解析傳遞過來的參數(shù)改艇。

/**
 * 通知欄點(diǎn)擊后的容器頁
 *
 * deeplink格式
 *
 *  jpush://com.xuexiang.jpush/notification?pageName=通知信息展示&title=這是一個(gè)通知&content=這是通知的內(nèi)容&extraMsg=xxxxxxxxx&keyValue={"param1": "1111", "param2": "2222"}
 *
 */
@Router(path = "/push/notification/transfer")
public class NotificationTransferActivity extends BaseActivity {

    @AutoWired
    String pageName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        XRouter.getInstance().inject(this);

        Uri uri = getIntent().getData();
        Bundle bundle = getIntent().getExtras();
        if (uri != null) {
            //deeplink跳轉(zhuǎn)
            pageName = uri.getQueryParameter("pageName");
            bundle = Utils.parseNotificationDeepLinkUri(uri, bundle);
        }

        if (!StringUtils.isEmpty(pageName)) {
            //打開指定頁面
            if (openPage(pageName, bundle) == null) {
                XToastUtils.toast("頁面未找到!");
                finish();
            }
        } else {
            XToastUtils.toast("頁面未找到坟岔!");
            finish();
        }
    }

    /**
     * DeepLink的格式:
     *      jpush://com.xuexiang.jpush/notification?pageName=xxxxx&title=這是一個(gè)通知&content=這是通知的內(nèi)容&extraMsg=xxxxxxxxx&keyValue={"param1": "1111", "param2": "2222"}
     * @param uri
     * @param bundle
     * @return
     */
    public static Bundle parseNotificationDeepLinkUri(@NonNull Uri uri, Bundle bundle) {
        if (bundle == null) {
            bundle = new Bundle();
        }

        bundle.putString("pageName", uri.getQueryParameter("pageName"));
        //通知標(biāo)題
        bundle.putString("title", uri.getQueryParameter("title"));
        //通知內(nèi)容
        bundle.putString("content", uri.getQueryParameter("content"));
        //通知附帶拓展內(nèi)容
        bundle.putString("extraMsg", uri.getQueryParameter("extraMsg"));
        //通知附帶鍵值對
        bundle.putString("keyValue", uri.getQueryParameter("keyValue"));
        return bundle;
    }
}

注意:上面的openPage方法主要使用了我的開源XPage,主要的作用就是Fragment頁面路由谒兄,加載一個(gè)Fragment頁面。

(3)發(fā)通知消息的時(shí)候社付,記得設(shè)置上自定義(打開指定頁面)的鏈接承疲,如下圖所示:

  • 鏈接示例,其中jpush://com.xuexiang.jpush/notification對應(yīng)上面AndroidManifest.xml中配置的信息鸥咖。
jpush://com.xuexiang.jpush/notification?pageName=xxxxx&title=這是一個(gè)通知&content=這是通知的內(nèi)容&extraMsg=xxxxxxxxx&keyValue={"param1": "1111", "param2": "2222"}
  • 設(shè)置示例:
通知設(shè)置

消息接收處理

同樣的燕鸽,自定義消息和通知消息都是在JPushMessageReceiver的回調(diào)方法中獲取,和上面的操作結(jié)果類似啼辣,JPushMessageReceiver最多只是作為消息的中轉(zhuǎn)站啊研,如果我們想要在任何頁面都能夠訂閱到接收到的消息,那么我們依舊可以和上面處理得一樣鸥拧,使用RxBus將這些消息向外發(fā)送出去党远。

下面我給出實(shí)現(xiàn)的簡要步驟:

1.定義消息的類型,這里暫時(shí)就是自定義消息和通知消息富弦。

/**
 * 消息的類型
 */
@IntDef({TYPE_CUSTOM, TYPE_NOTIFICATION})
@Retention(RetentionPolicy.SOURCE)
public @interface MessageType {
    /**
     * 自定義消息
     */
    int TYPE_CUSTOM = 1000;
    /**
     * 普通通知消息
     */
    int TYPE_NOTIFICATION = 1001;
}

2.定義推送消息的載體.

目前為了偷懶沟娱,暫時(shí)就只定義了兩個(gè)成員變量:mType(消息類型)和mMessage(消息數(shù)據(jù))。如下所示:

/**
 * 推送消息
 */
public final class PushMessage {
    public static final String KEY_PUSH_MESSAGE = "com.xuexiang.jpushsample.core.push.event.KEY_PUSH_MESSAGE";
    /**
     * 消息類型
     */
    private int mType;
    /**
     * 消息數(shù)據(jù)
     */
    private Object mMessage;

    public static PushMessage wrap(@MessageType int type, Object message) {
        return new PushMessage(type, message);
    }

    public PushMessage(@MessageType int type, Object message) {
        mType = type;
        mMessage = message;
    }

    public int getType() {
        return mType;
    }

    public PushMessage setType(int type) {
        mType = type;
        return this;
    }

    public <T> T getMessage() {
        return (T) mMessage;
    }

    public PushMessage setMessage(Object message) {
        mMessage = message;
        return this;
    }

    public String getMessageType() {
        switch (mType) {
            case TYPE_CUSTOM:
                return "自定義消息";
            case TYPE_NOTIFICATION:
                return "普通通知消息";
            default:
                return "未知消息";
        }
    }
}

3.消息接收并分發(fā).

JPushMessageReceiver中重寫onMessageonNotifyMessageArrived方法舆声,并將結(jié)果轉(zhuǎn)譯為一個(gè)個(gè)PushMessage發(fā)送出去花沉。

/**
 * 極光推送消息接收器
 */
public class PushMessageReceiver extends JPushMessageReceiver {
    private static final String TAG = "JPush-Receiver";
    /**
     * 收到自定義消息回調(diào)
     *
     * @param context
     * @param message 自定義消息
     */
    @Override
    public void onMessage(Context context, CustomMessage message) {
        Log.e(TAG, "[onMessage]:" + message);
        RxBusUtils.get().post(KEY_PUSH_MESSAGE, PushMessage.wrap(MessageType.TYPE_CUSTOM, message));
    }
    /**
     * 收到通知回調(diào)
     *
     * @param context
     * @param message 通知消息
     */
    @Override
    public void onNotifyMessageArrived(Context context, NotificationMessage message) {
        Log.e(TAG, "[onNotifyMessageArrived]:" + message);
        RxBusUtils.get().post(KEY_PUSH_MESSAGE, PushMessage.wrap(MessageType.TYPE_NOTIFICATION, message));
    }
}

此外柳爽,如果因業(yè)務(wù)需要媳握,在消息發(fā)送出去之前,我們還可以在發(fā)送前添加一個(gè)過濾器處理磷脯,對一些重復(fù)蛾找、無效的消息進(jìn)行過濾,或者對同時(shí)接收到的消息進(jìn)行消息合并等操作赵誓。

4.在需要獲取推送消息的地方訂閱打毛。

@Override
protected void initListeners() {
    //訂閱消息
    RxBusUtils.get().onMainThread(KEY_PUSH_MESSAGE, PushMessage.class, this::handlePushMessage);
}
/**
 * 處理接收到的推送消息
 */
private void handlePushMessage(PushMessage pushMessage) {
    tvType.setText(pushMessage.getMessageType());
    tvMessage.setText(pushMessage.getMessage().toString());
}
@Override
public void onDestroyView() {
    //取消訂閱
    RxBusUtils.get().unregisterAll(KEY_PUSH_MESSAGE);
    super.onDestroyView();
}

結(jié)尾

好啦,以上就是《極光推送之Android客戶端使用指南--基礎(chǔ)篇》的全部內(nèi)容,本文主要介紹了極光推送最常用也是最基礎(chǔ)的幾部分內(nèi)容,相信如果你能完全掌握上面的內(nèi)容的話,推送基本上也算是駕輕就熟了.下面我還將深入挖掘極光推送的幾個(gè)高級(jí)使用,敬請期待吧!

關(guān)聯(lián)鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俩功,一起剝皮案震驚了整個(gè)濱河市幻枉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诡蜓,老刑警劉巖熬甫,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蔓罚,居然都是意外死亡椿肩,警方通過查閱死者的電腦和手機(jī)瞻颂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來郑象,“玉大人贡这,你說我怎么就攤上這事〕ч唬” “怎么了盖矫?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長击奶。 經(jīng)常有香客問我炼彪,道長,這世上最難降的妖魔是什么正歼? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任辐马,我火速辦了婚禮,結(jié)果婚禮上局义,老公的妹妹穿的比我還像新娘喜爷。我一直安慰自己,他們只是感情好萄唇,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布檩帐。 她就那樣靜靜地躺著,像睡著了一般另萤。 火紅的嫁衣襯著肌膚如雪湃密。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天四敞,我揣著相機(jī)與錄音泛源,去河邊找鬼。 笑死忿危,一個(gè)胖子當(dāng)著我的面吹牛达箍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铺厨,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼缎玫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了解滓?” 一聲冷哼從身側(cè)響起赃磨,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洼裤,沒想到半個(gè)月后邻辉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年恩沛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了在扰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雷客,死狀恐怖芒珠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搅裙,我是刑警寧澤皱卓,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站部逮,受9級(jí)特大地震影響娜汁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兄朋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一掐禁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颅和,春花似錦傅事、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至教届,卻和暖如春响鹃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背案训。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工买置, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人萤衰。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓堕义,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脆栋。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容