2019-05-10 簡(jiǎn)單集成華為推送(老版本Push SDK)

May 14, 2019 添加更新,對(duì)于Android O(API 26)及以上的手機(jī)死遭,如果target API ≥ 26(Android 8.0),則必須自己生成NotificationChannel,否則推送無法顯示消息朱巨,8.0之前的不會(huì)受影響,系統(tǒng)默認(rèn)生成一個(gè)NotificationChannel枉长,使用NotificationManager.createNotificationChannel()可以生成NotificationChannel冀续。

一,環(huán)境

華為榮耀V8 EMUI 8.0.0 Android 8.0.0
Android Studio 3.3.2
華為推送版本 2.5.2.300
SDK集成方式:
參考華為開發(fā)者聯(lián)盟>HMS>資源中心>消息推送服務(wù)>集成SDK

華為推送分為新老兩個(gè)版本必峰,注意區(qū)別洪唐,新版本SDK為HMS Push,老版本切換到新版本需要更新SHA256證書指紋

二吼蚁,簡(jiǎn)介

  1. 端內(nèi)推送
    App<->推送服務(wù)器長(zhǎng)連接凭需,服務(wù)器下發(fā)推送消息給App進(jìn)程,App進(jìn)程通過NotificationManager在通知欄進(jìn)行顯示肝匆,比如IM粒蜈,運(yùn)動(dòng)跟蹤之類的應(yīng)用,優(yōu)點(diǎn)是速度快旗国,能夠自行保證送達(dá)率枯怖,延時(shí)小,缺點(diǎn)是App進(jìn)程在被系統(tǒng)或用戶手動(dòng)kill掉后能曾,無法收到推送度硝,一般大廠在做好端外推送的同時(shí)也會(huì)自己做端內(nèi)推送设捐,小公司就不要考慮那么多了。
  2. 端外推送
    當(dāng)App<->推送服務(wù)器長(zhǎng)連接斷開后塘淑,推送走的就是端外推送了萝招,因?yàn)椴灰蕾嘇pp客戶端,靠的只能是第三方推送平臺(tái)提供的服務(wù)存捺,主要分成三大類槐沼,手機(jī)廠商,專業(yè)的第三方推送捌治,BAT的推送岗钩,比如(排名不分先后):小米,華為肖油,個(gè)推兼吓,極光,百度云森枪,阿里云移動(dòng)视搏,騰訊信鴿等。

作為默默無聞的小公司县袱,端內(nèi)推送開發(fā)維護(hù)成本太高浑娜,直接忽略不考慮;
專業(yè)的第三方推送反而比較適合式散,因?yàn)樘峁┝朔奖泯R全的功能筋遭,比如個(gè)推,但是個(gè)推也會(huì)因?yàn)楦鞣N原因有推送不達(dá)的情況暴拄,所以在個(gè)推基礎(chǔ)上可以補(bǔ)充一點(diǎn)廠商的推送漓滔,至于具體選擇哪個(gè)可以參考文章連接:Android 端外推送到底有多煩?(https://juejin.im/post/57a19c012e958a0066715d0c)選擇合適自己方案

我選擇的是個(gè)推乖篷,華為和小米响驴,選擇華為和小米的原因也很無奈,這兩者市場(chǎng)最大那伐,用戶使用這兩種手機(jī)較多踏施,使用個(gè)推又一直有推送不達(dá)的情況石蔗,公司主營(yíng)的業(yè)務(wù)又對(duì)推送的送達(dá)率有較高要求罕邀,只能集成華為和小米推送來獲得更好的送達(dá)率

這里只討論華為推送集成,但是在App初始化的時(shí)候會(huì)選擇適合自己手機(jī)的推送方式养距,即華為手機(jī)上選擇華為推送诉探,小米手機(jī)選擇小米推送,其他手機(jī)選擇個(gè)推

參考文章:
[Android] 代碼獲取手機(jī)系統(tǒng)類型(小米MIUI棍厌、華為EMUI肾胯、魅族FLYME)
Android常見ROM類型識(shí)別MD
獲取系統(tǒng)手機(jī)類型

    public static final String SYS_EMUI = "sys_emui";
    public static final String SYS_MIUI = "sys_miui";
    private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code";
    private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name";
    private static final String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage";
    private static final String KEY_EMUI_API_LEVEL = "ro.build.hw_emui_api_level";
    private static final String KEY_EMUI_VERSION = "ro.build.version.emui";
    private static final String KEY_EMUI_CONFIG_HW_SYS_VERSION = "ro.confg.hw_systemversion";

    public static String getSystem(){
        String SYS = "";
        //Android API 26及以后會(huì)有Permission deny的情況竖席,需要使用反射機(jī)制來獲取
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
            if (!TextUtils.isEmpty(getSystemProperty(KEY_MIUI_VERSION_CODE, ""))
                    || !TextUtils.isEmpty(getSystemProperty(KEY_MIUI_VERSION_NAME, ""))
                    || !TextUtils.isEmpty(getSystemProperty(KEY_MIUI_INTERNAL_STORAGE, ""))) {
                SYS = SYS_MIUI;//小米
            }else if (!TextUtils.isEmpty(getSystemProperty(KEY_EMUI_API_LEVEL, ""))
                    || !TextUtils.isEmpty(getSystemProperty(KEY_EMUI_VERSION, ""))
                    || !TextUtils.isEmpty(getSystemProperty(KEY_EMUI_CONFIG_HW_SYS_VERSION, ""))) {
                SYS = SYS_EMUI;//華為
            }
            return SYS;
        } else {
            try {
                Properties prop= new Properties();
                prop.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
                if(prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null
                        || prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null
                        || prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null){
                    SYS = SYS_MIUI;//小米
                }else if(prop.getProperty(KEY_EMUI_API_LEVEL, null) != null
                        ||prop.getProperty(KEY_EMUI_VERSION, null) != null
                        ||prop.getProperty(KEY_EMUI_CONFIG_HW_SYS_VERSION, null) != null){
                    SYS = SYS_EMUI;//華為
                }
            } catch (IOException e){
                HHLog.e(TAG, "error, info:" + Log.getStackTraceString(e));
                e.printStackTrace();
            }
            return SYS;
        }
    }

根據(jù)手機(jī)系統(tǒng)類型不同,把對(duì)應(yīng)系統(tǒng)的token敬肚、推送平臺(tái)以及其他推送相關(guān)的配置信息上傳給應(yīng)用服務(wù)器毕荐,比如使用的是華為手機(jī),就需要把華為推送的token上傳艳馒;如果是小米手機(jī)憎亚,就需要把小米推送的regid上傳,服務(wù)器根據(jù)不同的推送配置信息通過指定的推送平臺(tái)發(fā)送消息弄慰,推送平臺(tái)再利用自己的渠道下發(fā)到手機(jī)終端第美。

三,集成過程

1. AndroidManifest.xml文件的配置

SDK的添加過程可以參考文章鏈接:華為開發(fā)者聯(lián)盟>HMS>資源中心>消息推送服務(wù)>AndroidStudio開發(fā)環(huán)境陆爽,這里不再贅述

<!--*********************華為推送***Start*********************-->
<!--替換成自己申請(qǐng)到的appid-->
<meta-data android:name="com.huawei.hms.client.appid"
    android:value="123456789"/>
<activity android:name="com.huawei.hms.activity.BridgeActivity"
    android:configChanges="orientation|locale|screenSize|layoutDirection|fontScale"
    android:excludeFromRecents="true"
    android:exported="false"
    android:hardwareAccelerated="true"
    android:theme="@android:style/Theme.Translucent">
    <meta-data android:name="hwc-theme"
        android:value="androidhwext:style/Theme.Emui.Translucent" />
</activity>
<provider android:authorities="com.hhws.camerafamily360.hms.update.provider"
    android:name="com.huawei.hms.update.provider.UpdateProvider"
    android:exported="false"
    android:grantUriPermissions="true"/>
<!--自定義廣播:接收Push消息(注冊(cè)什往、Push消息、Push連接狀態(tài)等)-->
<receiver android:name="com.vihivision.camerafamilyKey.hwpush.HuaweiPushReceiver">
    <intent-filter>
        <!-- 必須,用于接收TOKEN -->
        <action android:name="com.huawei.android.push.intent.REGISTRATION" />
        <!-- 必須慌闭,用于接收消息 -->
        <action android:name="com.huawei.android.push.intent.RECEIVE" />
        <!-- 可選别威,用于點(diǎn)擊通知欄或通知欄上的按鈕后觸發(fā)onEvent回調(diào) -->
        <action android:name="com.huawei.android.push.intent.CLICK" />
        <!-- 可選,查看PUSH通道是否連接驴剔,不查看則不需要 -->
        <action android:name="com.huawei.intent.action.PUSH_STATE" />
    </intent-filter>
</receiver>
<receiver android:name="com.huawei.hms.support.api.push.PushEventReceiver" >
    <intent-filter>
        <!-- 接收通道發(fā)來的通知欄消息兔港,兼容老版本PUSH -->
        <action android:name="com.huawei.intent.action.PUSH" />
    </intent-filter>
</receiver>
<!--*********************華為推送***End*********************-->

2. 自定義廣播HuaweiPushReceiver

自定義的廣播HuaweiPushReceiver必須聲明在AndroidManifest.xml文件中,HuaweiPushReceiver必須重寫4個(gè)回調(diào)方法:onToken仔拟,onPushMsg,onEvent利花,onPushState科侈,分別對(duì)應(yīng)接收token,接收透?jìng)飨⒊词拢ㄖ獧邳c(diǎn)擊事件回調(diào)臀栈,push連接狀態(tài)

package com.vihivision.camerafamilyKey.hwpush;

import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;

import com.huawei.hms.support.api.push.PushReceiver;
// import com.vihivision.camerafamilyKey.base.MyApplication;
import com.vihivision.camerafamilyKey.util.AXLog;
import com.vihivision.camerafamilyKey.util.NotificactionUtil;

public class HuaweiPushReceiver extends PushReceiver {
    private static final String TAG = "HuaweiPushReceiver";

    /**
     * 連接上華為服務(wù)時(shí)回調(diào),可以獲取token值
     * @param context
     * @param token
     * @param extras
     * */
    @Override
    public void onToken(Context context, String token, Bundle extras) {
        String belongId = extras.getString("belongId");
        //MyApplication.PUST_CLIENTID_HW = token;//保存token,需要上傳到應(yīng)用服務(wù)器挠乳,以便應(yīng)用服務(wù)器根據(jù)token發(fā)送消息
        String content = "華為推送get token and belongId successful, token = " + token + ",belongId = " + belongId;
        AXLog.e(TAG, content);
    }

    /**
     * 透?jìng)飨⒌幕卣{(diào)方法
     * @param context
     * @param msg 推送消息內(nèi)容
     * @param bundle
     * */
    @Override
    public boolean onPushMsg(Context context, byte[] msg, Bundle bundle) {
        try {
            String content = new String(msg, "UTF-8");
            boolean bisRuning = false;
            if (content != null) {
                bisRuning = !MyApplication.isRunInBackground;
                AXLog.e(TAG,"收到推送消息 是否后臺(tái)運(yùn)行:"+ bisRuning);
                //處理透?jìng)鞯南⑷ㄊ恚梢造o默也可以通過NotificationManager來展示推送消息
                NotificactionUtil.jsonPareChnnel(content,bisRuning);//自定義
                AXLog.e(TAG,"alarmMessage:"+content);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 自定義的消息的回調(diào)方法
     * @param context
     * @param event
     * @param extras
     * */
    @Override
    public void onEvent(Context context, PushReceiver.Event event, Bundle extras) {
        AXLog.e(TAG,"event:"+event.toString()+" extras:"+extras);
        if (Event.NOTIFICATION_OPENED.equals(event) || Event.NOTIFICATION_CLICK_BTN.equals(event)) {
            int notifyId = extras.getInt(BOUND_KEY.pushNotifyId, 0);
            if (0 != notifyId) {
                NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
                manager.cancel(notifyId);
            }
            //可以對(duì)extras.getString返回的鍵值對(duì)數(shù)據(jù)進(jìn)行處理
            String content = "華為推送--------receive extented notification message: " + extras.getString(BOUND_KEY.pushMsgKey);
            AXLog.e(TAG, content);
        }
        super.onEvent(context, event, extras);
    }

    /**
     * 連接狀態(tài)的回調(diào)方法
     * @param context
     * @param pushState
     * */
    @Override
    public void onPushState(Context context, boolean pushState) {
        try {
            String content = "華為推送---------The current push status: " + (pushState ? "Connected" : "Disconnected");
            AXLog.e(TAG, content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3. 華為推送客戶端實(shí)例化

//DCloudApplication
public class MyApplication extends DCloudApplication {
    private final static String TAG = "MyApplication";
    public static Application MyAPP;
    public static Context applicationContext;

    //NotificationChannel ID
    public final static String NOTIFICATION_CHANNEL_ID = "360";
    //NotificationChannel Name
    public final static String NOTIFICATION_CHANNEL_NAME = "Anxin360";
    HuaweiApiClient client;

    @Override
    public void onCreate() {
        super.onCreate();
        MyAPP = this;
        applicationContext = getApplicationContext();

        //創(chuàng)建華為移動(dòng)服務(wù)client實(shí)例用以使用華為push服務(wù)
        //需要指定api為HuaweiPush.PUSH_API
        //連接回調(diào)以及連接失敗監(jiān)聽
        client = new HuaweiApiClient.Builder(this)
                .addApi(HuaweiPush.PUSH_API)
                .addConnectionCallbacks(new HuaweiApiClient.ConnectionCallbacks() {
                    @Override public void onConnected() {
                        //華為移動(dòng)服務(wù)client連接成功,在這邊處理業(yè)務(wù)自己的事件
                        AXLog.i(TAG, "HuaweiApiClient 連接成功");
                        getTokenAsyn();
                    }
                    @Override public void onConnectionSuspended(int i) {
                        //HuaweiApiClient斷開連接的時(shí)候睡扬,業(yè)務(wù)可以處理自己的事件
                        AXLog.i(TAG, "HuaweiApiClient 連接斷開");
                        // client.connect();
                    }
                })
                .addOnConnectionFailedListener(new HuaweiApiClient.OnConnectionFailedListener() {
                    @Override
                    public void onConnectionFailed(ConnectionResult arg0) {
                        AXLog.i(TAG, "HuaweiApiClient連接失敗盟蚣,錯(cuò)誤碼:" + arg0.getErrorCode());
                    }

                })
                .build();
        client.connect();

        //API 26及以上需要設(shè)置NotificationChannel
        NotificationManager manager = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
            notificationChannel.setBypassDnd(true);
            notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
            notificationChannel.setShowBadge(true);
            manager.createNotificationChannel(notificationChannel);
        }
    }

    private void getTokenAsyn() {
        if (!client.isConnected()) {
            AXLog.e(TAG, "獲取TOKEN失敗,原因:HuaweiApiClient未連接");
            return;
        }
        PendingResult<TokenResult> tokenResult = HuaweiPush.HuaweiPushApi.getToken(client);
        tokenResult.setResultCallback(new ResultCallback<TokenResult>() {
            @Override
            public void onResult(TokenResult result) {
                AXLog.e(TAG,"異步回調(diào)接口result:"+result.getTokenRes().getToken());
            }
        });
    }
}

4. 測(cè)試推送

測(cè)試推送的方式有兩種卖怜,一是應(yīng)用服務(wù)器通過API向華為Push服務(wù)器發(fā)消息屎开,這種需要服務(wù)器同步開發(fā);二是通過華為開發(fā)者聯(lián)盟提供的PUSH服務(wù)來發(fā)送消息马靠。
推薦選擇第二種奄抽,因?yàn)榉?wù)器開發(fā)需要時(shí)間蔼两,另外可能有bug,如果出現(xiàn)推送問題逞度,沒辦法搞清楚究竟是哪部分的問題额划,先通過華為開發(fā)者聯(lián)盟可以把App程序調(diào)通,而后再協(xié)助服務(wù)器開發(fā)調(diào)試比較合適档泽。

使用華為開發(fā)者聯(lián)盟推送測(cè)試消息需要先獲取到手機(jī)的token锁孟,可以通過打印日志的方式把token打印出來,這個(gè)token在應(yīng)用安裝后就是固定的茁瘦,卸載重裝會(huì)重置品抽。
發(fā)送推送的方式參考鏈接:華為開發(fā)者聯(lián)盟>HMS>資源中心>消息推送服務(wù)>發(fā)送消息

四,總結(jié)

體驗(yàn)了一下甜熔,透?jìng)飨⒃趉ill應(yīng)用進(jìn)程的情況下圆恤,一次都沒有收到,前后臺(tái)存活的情況下腔稀,100%送達(dá)盆昙,速度也很快,大概延遲在2~3秒左右焊虏;如果使用通知欄消息淡喜,則應(yīng)用存活與否都能成功送達(dá),但是速度比較慢诵闭,實(shí)測(cè)的話需要5~10秒甚至更長(zhǎng)炼团。
對(duì)比iOS的APNs,Android上的推送還是非常麻煩的疏尿,開發(fā)起來也很讓人煩躁瘟芝,大概是因?yàn)榧赡敲炊嗤扑停Y(jié)果還是不能保證送達(dá)吧褥琐!國外使用的是谷歌推送锌俱,基本沒有這些問題,類似蘋果的APNs敌呈,國內(nèi)因?yàn)楦鞣N原因包括廠商定制化的原因贸宏,導(dǎo)致Android開發(fā)者很難受,不過現(xiàn)在國內(nèi)有在推進(jìn)統(tǒng)一推送聯(lián)盟磕洪,希望以后推送不要這么難搞了吭练,最新的消息看到是已經(jīng)出了一個(gè)《統(tǒng)一推送技術(shù)要求和測(cè)試方法》(2019-2),有興趣的朋友可以持續(xù)跟進(jìn)褐鸥!

參考鏈接:
[華為官方]推送服務(wù)客戶端開發(fā)指南
[華為論壇-花粉俱樂部]【獲獎(jiǎng)名單】華為推送負(fù)責(zé)人揭曉Push成功率翻倍要訣

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末线脚,一起剝皮案震驚了整個(gè)濱河市赐稽,隨后出現(xiàn)的幾起案子叫榕,更是在濱河造成了極大的恐慌浑侥,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晰绎,死亡現(xiàn)場(chǎng)離奇詭異寓落,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荞下,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門伶选,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尖昏,你說我怎么就攤上這事仰税。” “怎么了抽诉?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵陨簇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我迹淌,道長(zhǎng)河绽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任唉窃,我火速辦了婚禮耙饰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纹份。我一直安慰自己苟跪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布蔓涧。 她就那樣靜靜地躺著削咆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蠢笋。 梳的紋絲不亂的頭發(fā)上拨齐,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音昨寞,去河邊找鬼瞻惋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛援岩,可吹牛的內(nèi)容都是我干的歼狼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼享怀,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼羽峰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤梅屉,失蹤者是張志新(化名)和其女友劉穎值纱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坯汤,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虐唠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惰聂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疆偿。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖搓幌,靈堂內(nèi)的尸體忽然破棺而出杆故,到底是詐尸還是另有隱情,我是刑警寧澤溉愁,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布反番,位于F島的核電站,受9級(jí)特大地震影響叉钥,放射性物質(zhì)發(fā)生泄漏罢缸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一投队、第九天 我趴在偏房一處隱蔽的房頂上張望枫疆。 院中可真熱鬧,春花似錦敷鸦、人聲如沸息楔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽值依。三九已至,卻和暖如春碟案,著一層夾襖步出監(jiān)牢的瞬間愿险,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工价说, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辆亏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓鳖目,卻偏偏與公主長(zhǎng)得像扮叨,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子领迈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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