Markdown版本筆記 | 我的GitHub首頁 | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目錄
小米推送介紹
【17329910039】【bqt中信賬號】
小米相比極光等推送SDK的優勢:
- MIUI上系統級通道:在MIUI上系統級長連接,最大程度提高消息送達率
- iOS/Android全平台支持:支持兩大系統的推送服務,iOS開發者還可以將存量用戶無縫遷移到小米推送中
- 穩定 安全 高效:每秒百萬級推送速度,億級同時在線,99.8%的消息300毫秒內到達
- 使用簡單靈活:客戶端0.5天集成,也可通過服務端API與業務邏輯相結合
- 細致全面的統計工具:提供細致全面的統計工具,幫助開發者精准把握推送的使用情況
- 自助調查工具:開發者可以自助查詢設備與消息的送達/在線情況
FAQ
小米推送服務有哪些限制?
目前針對首批合作開發者,小米推送服務沒有設置任何推送頻率的使用限制,之后出於防止惡意應用攻擊等考慮,可能會增加對推送消息的頻率、對單一用戶可以接收的數量等做一些限制,但不會影響開發者的正常使用。而且所提供的推送服務完全免費。 對於單條消息,可攜帶的數據量最大不能超過4KB。
Android版推送中,多個app都使用推送時,他們會共享連接嗎?
在最新MIUI上,會直接使用系統長連接通道,所有app會和系統共享一個長連接;在其他rom上,目前沒有共享連接。多通道的設計從通道安全性和流量計算上會更加合理,並且小米推送的多通道實現可以保證多條長連接對系統電量的影響和一條長連接基本相同。
什么是透傳?
透傳類推送是指開發者可選擇不通過任何預定義的方式展現,由應用直接接收推送消息。利用透傳消息,開發者可自定義更多使用推送的方式和展現形式,從而能更為靈活地使用消息推送通道。 在一些擁有應用啟動管理功能的Android系統上(如MIUI),透傳的實現需要應用在后台處於啟動狀態。
透傳和通知欄,在送達率上有什么分別?
- 首先解釋一下透傳和通知欄方式的原理,透傳是指當小米推送服務客戶端SDK接收到消息之后,直接把消息通過回調方法發送給應用,不做任何處理;而通知欄方式,則在設備接收到消息之后,首先由小米推送服務SDK彈出標准安卓通知欄通知,在用戶點擊通知欄之后,激活應用。
- 在非MIUI系統中,由於維護小米推送服務長連接的service是寄生在App的運行空間當中的,因此透傳和通知欄方式在送達率上並沒有任何區別,都需要應用駐留在后台。即,如果一台設備通知欄消息能夠接收到並彈出,那么其透傳消息也同樣能接收到。
- 在MIUI系統中,由於長連接是由MIUI系統服務建立並維護的,因此在接收消息的時候並不需要應用駐留后台。如果采用通知欄方式接收消息,由於通知欄也是MIUI系統服務彈出的,就可以做到不需要用戶后台駐留或者可以自啟動消息就能送達。而如果采用透傳消息,由於需要直接執行應用的代碼,因此即使消息已經到了系統服務,如果應用沒有駐留后台或者能自啟動,消息依然不能送達,需等下次用戶手動點擊激活應用后,才能接收到消息。
- 綜上,在MIUI系統中,通知欄消息的送達率會遠高於透傳方式;在非MIUI系統中,通知欄和透傳方式的送達率是一樣的。
使用推送服務demo沒有成功是什么原因?
一般注冊推送服務沒成功,常見的原因如下:
- 1.沒有開啟推送服務。使用推送服務前需要登錄開發者賬號、創建應用、開通推送服務3個步驟,缺一不可。
- 2.沒有正確配置AndroidManifest.xml文件。需要特別注意包名、權限部分,包名需要跟開發者站點上開通推送服務的一致,權限的前綴需要改成包名。
- 3.系統時間錯誤。由於小米推送服務需要使用https請求向服務器注冊一個匿名賬號,在次過程中如果系統時間錯誤,會引起https過期,導致注冊不成功。
- 4.聯網被阻止。小米推送服務客戶端需要使用5222和443兩個端口,如果在公司內網,需要聯系IT部門把這兩個端口開放。同時需要檢查應用的聯網是否會被一些手機安全助手阻止。需要特別注意的是,在MIUI系統上,長連接是由“小米服務框架”這個系統應用維護的,因此需要確保這個應用的聯網並沒有被阻止。
如果我使用通知欄類型消息,能否在通知欄消息到達之前,先執行一段app的代碼?或者在通知欄到達時,通知app?
在MIUI系統上,通知欄類型的消息,是不需要應用啟動就能彈出的(這一特性決定了通知欄消息的彈出可以不受應用自啟動管理的影響),因此在整個彈出通知欄消息的過程中,app是完全不可感知的,當用戶點擊通知欄消息之后,才會執行到app的代碼。
為什么onNotificationMessageArrived方法沒被調用到?
- 首先,確定你的接入是否正確,這個方法需要在manifest中添加
<action android:name=”com.xiaomi.mipush.MESSAGE_ARRIVED” /
> 這個action。 在接入正確的前提下,這個方法也不是保證一定能被調用的。 - 在MIUI系統上,這個方法的調用需要同時滿足如下兩個條件:
- 1.新版的MIUI。這個特性是在2015年才加進小米推送服務的,因此需要MIUI升級到較新的版本才能調用這個方法。
- 2.需要應用駐留后台。小米推送服務的通知欄消息,是可以在應用不啟動的前提下,就彈出通知欄消息的,在這種情況下, 由於MIUI的自啟動管理,限制了應用不能在被殺后被后台喚醒,所以推送消息不能直接喚醒應用執行這個方法。
為什么我在onNotificationMessageClicked方法中的startActivity不能調起目標界面?
由於onNotificationMessageClicked中傳入的context是application context,本身沒有activity棧,因此需要在創建activity時候加入NEW_TASK的flag:
Intent i = new Intent(context, MyActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
當我的應用被殺掉之后,還能否接收到小米推送服務的消息?
有如下幾種情況:如果是在MIUI系統中,使用通知欄類型的消息,是不需要應用出於啟動狀態就能接收並彈出通知欄的。使用透傳消息,則需要應用駐留后台才能接收,由於MIUI的自啟動管理限制,所以如果應用被殺,是收不到透傳消息的。而如果是在非MIUI系統中,是需要應用駐留后台才能接收消息的,因此如果應用被殺死並且不能后台自啟動的話,是沒有辦法接收消息的。為了讓app盡可能的駐留后台,小米推送服務SDK監聽了網絡變化等系統事件,並且有應用之間的互相喚醒,但這些措施並不能保證應用可以一直在后台駐留。
簡潔版自定義消息推送Demo
在適當的時候初始化
private void initMiPush() {
String APP_ID = "1000270";
String APP_KEY = "670100056270";
// 注冊push服務,注冊成功后會向Receiver發送廣播,可以從onCommandResult方法中的參數中獲取注冊信息
if (PushUtil.shouldInitMiPush(this)) {
MiPushClient.registerPush(this, APP_ID, APP_KEY);
}
}
public static boolean shouldInitMiPush(Context context) {
ActivityManager am = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE));
if (am != null) {
List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
String mainProcessName = context.getPackageName();
int myPid = Process.myPid();
for (ActivityManager.RunningAppProcessInfo info : processInfos) {
if (info.pid == myPid && mainProcessName.equals(info.processName)) {
return true;
}
}
}
return false;
}
Activity
/**
* 1、設置 topic 和 alias。 服務器端使用 appsecret 即可以向demo發送廣播和單點的消息。<br/>
* 2、為了修改本 demo 為使用你自己的 appid,你需要修改幾個地方:Application中的 APP_ID 和 APP_KEY
* AndroidManifest.xml 中的 packagename,和權限 permission.MIPUSH_RECEIVE 的前綴為你的 packagename。
*/
public class MiPushTestActivity extends ListActivity {
public static boolean isForeground = false;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"設置別名",
"撤銷別名",
"設置帳號",
"撤銷帳號",
"設置標簽",
"撤銷標簽",
"設置接收消息時間",
"暫停推送",
"重新開始推送",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
}
@Override
protected void onResume() {
isForeground = true;
super.onResume();
}
@Override
protected void onPause() {
isForeground = false;
super.onPause();
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
set_alias();
break;
case 1:
unset_alias();
break;
case 2:
set_account();
break;
case 3:
unset_account();
break;
case 4:
subscribe_topic();
break;
case 5:
unsubscribe_topic();
break;
case 6:
int startHour = 10;
int startMin = 0;
int endHour = 23;
int endMin = 0;
MiPushClient.setAcceptTime(this, startHour, startMin, endHour, endMin, null);
break;
case 7:
MiPushClient.pausePush(this, null);
break;
case 8:
MiPushClient.resumePush(this, null);
break;
}
}
public void set_alias() {
final EditText editText = new EditText(this);
new AlertDialog.Builder(this)
.setTitle("設置別名")
.setView(editText)
.setPositiveButton("確認", (dialog, which) -> {
String alias = editText.getText().toString();
MiPushClient.setAlias(this, alias, null);
})
.setNegativeButton("取消", null)
.show();
}
public void unset_alias() {
final EditText editText = new EditText(this);
new AlertDialog.Builder(this)
.setTitle("撤銷別名")
.setView(editText)
.setPositiveButton("確認", (dialog, which) -> {
String alias = editText.getText().toString();
MiPushClient.unsetAlias(this, alias, null);
})
.setNegativeButton("取消", null)
.show();
}
public void set_account() {
final EditText editText = new EditText(this);
new AlertDialog.Builder(this)
.setTitle("設置帳號")
.setView(editText)
.setPositiveButton("確認", (dialog, which) -> {
String account = editText.getText().toString();
MiPushClient.setUserAccount(this, account, null);
})
.setNegativeButton("取消", null)
.show();
}
public void unset_account() {
final EditText editText = new EditText(this);
new AlertDialog.Builder(this)
.setTitle("撤銷帳號")
.setView(editText)
.setPositiveButton("確認", (dialog, which) -> {
String account = editText.getText().toString();
MiPushClient.unsetUserAccount(this, account, null);
})
.setNegativeButton("取消", null)
.show();
}
public void subscribe_topic() {
final EditText editText = new EditText(this);
new AlertDialog.Builder(this)
.setTitle("設置標簽")
.setView(editText)
.setPositiveButton("確認", (dialog, which) -> {
String topic = editText.getText().toString();
MiPushClient.subscribe(this, topic, null);
})
.setNegativeButton("取消", null)
.show();
}
public void unsubscribe_topic() {
final EditText editText = new EditText(this);
new AlertDialog.Builder(this)
.setTitle("撤銷標簽")
.setView(editText)
.setPositiveButton("確認", (dialog, which) -> {
String topic = editText.getText().toString();
MiPushClient.unsubscribe(this, topic, null);
})
.setNegativeButton("取消", null)
.show();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onPushEvent(BasePushBean bean) {
TextView tv = new TextView(this);
tv.setTextColor(Color.BLUE);
tv.setText(bean.msg);
getListView().addFooterView(tv);
}
}
Receiver
/**
* 注意,重寫的這些方法都運行在非 UI 線程中
*/
public class MiPushReceiver extends PushMessageReceiver {
@Override
/*用來接收服務器向客戶端發送的透傳消息*/
public void onReceivePassThroughMessage(Context context, MiPushMessage message) {
if (message == null) return;
Log.i("bqt", "【onReceivePassThroughMessage】" + message.toString());
printMsg(message);
new Handler(Looper.getMainLooper()).post(() -> PushMsgReceiverHelper.getInstance().onMiPushMsgReceiver(message));
}
@Override
/*用來接收服務器向客戶端發送的通知消息,這個回調方法會在用戶手動點擊通知后觸發*/
public void onNotificationMessageClicked(Context context, MiPushMessage message) {
Log.i("bqt", "【onNotificationMessageClicked】" + message.toString());
printMsg(message);
}
@Override
/*用來接收服務器向客戶端發送的通知消息,這個回調方法是在通知消息到達客戶端時觸發。
另外應用在前台時不彈出通知的通知消息到達客戶端也會觸發這個回調函數。*/
public void onNotificationMessageArrived(Context context, MiPushMessage message) {
Log.i("bqt", "【onNotificationMessageArrived】" + message.toString());
printMsg(message);
}
@Override
/*用來接收客戶端向服務器發送命令后的響應結果*/
public void onCommandResult(Context context, MiPushCommandMessage message) {
Log.i("bqt", "【onCommandResult】" + message.toString());
printCmdMsg(message);
}
@Override
/*用來接收客戶端向服務器發送注冊命令后的響應結果*/
public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {
Log.i("bqt", "【onReceiveRegisterResult】" + message.toString());
String command = message.getCommand();
List<String> arguments = message.getCommandArguments();
String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
Log.i("bqt", "command=" + command + " " + cmdArg1);
}
private void printMsg(MiPushMessage message) {
String topic = message.getTopic();
String alias = message.getAlias();
String content = message.getContent();
Log.i("bqt", "topic=" + topic + " alias=" + alias + " content=" + content);
}
private void printCmdMsg(MiPushCommandMessage message) {
String command = message.getCommand();
List<String> arguments = message.getCommandArguments();
String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null);
Log.i("bqt", "command=" + command + " " + cmdArg1 + " " + cmdArg2);
switch (command) {
case MiPushClient.COMMAND_REGISTER://注冊
if (message.getResultCode() == ErrorCode.SUCCESS) {
Log.i("bqt", "注冊成功 mRegId =" + cmdArg1);
}
break;
case MiPushClient.COMMAND_SET_ALIAS://設置別名
if (message.getResultCode() == ErrorCode.SUCCESS) {
Log.i("bqt", "設置別名成功 mAlias =" + cmdArg1);
}
break;
case MiPushClient.COMMAND_UNSET_ALIAS://取消設置別名
if (message.getResultCode() == ErrorCode.SUCCESS) {
Log.i("bqt", "取消設置別名成功 mAlias =" + cmdArg1);
}
break;
case MiPushClient.COMMAND_SET_ACCOUNT://設置賬戶
if (message.getResultCode() == ErrorCode.SUCCESS) {
Log.i("bqt", "設置賬戶成功 mAccount =" + cmdArg1);
}
break;
case MiPushClient.COMMAND_UNSET_ACCOUNT://撤銷賬戶
if (message.getResultCode() == ErrorCode.SUCCESS) {
Log.i("bqt", "撤銷賬戶成功 mAccount =" + cmdArg1);
}
break;
case MiPushClient.COMMAND_SUBSCRIBE_TOPIC://訂閱標簽
if (message.getResultCode() == ErrorCode.SUCCESS) {
Log.i("bqt", "訂閱標簽成功 mTopic =" + cmdArg1);
}
break;
case MiPushClient.COMMAND_UNSUBSCRIBE_TOPIC://撤銷標簽
if (message.getResultCode() == ErrorCode.SUCCESS) {
Log.i("bqt", "撤銷標簽成功 mTopic =" + cmdArg1);
}
break;
case MiPushClient.COMMAND_SET_ACCEPT_TIME://設置接收推送時間
if (message.getResultCode() == ErrorCode.SUCCESS) {
Log.i("bqt", "設置接收推送時間成功 mStartTime =" + cmdArg1 + " mEndTime=" + cmdArg2);
}
break;
default:
Log.i("bqt", "未知命令,Reason=" + message.getReason());
break;
}
}
}
消息處理類
/**
* 處理推送SDK推過來的自定義消息(又叫應用內消息,或者透傳消息)
*/
public class PushMsgReceiverHelper {
private static PushMsgReceiverHelper instance = new PushMsgReceiverHelper();
private PushMsgReceiverHelper() {
}
public static PushMsgReceiverHelper getInstance() {
return instance;
}
/**
* 處理極光推送推過來的自定義消息
*/
public void onJPushMsgReceiver(Bundle bundle) {
}
/**
* 處理小米推送推過來的自定義消息
*/
public void onMiPushMsgReceiver(MiPushMessage message) {
Log.i("bqt", "【小米推送】" + message);
if (MiPushTestActivity.isForeground) {
EventBus.getDefault().post(new BasePushBean(message.getContent(), BasePushBean.TYPE_STRING));
}
}
/**
* 處理華為光推送推過來的自定義消息
*/
public void onHuaweiPushMsgReceiver(String message) {
}
/**
* 處理魅族推送推過來的自定義消息
*/
public void onMeiZhuPushMsgReceiver(String message) {
}
}
配置文件
build.gradle
implementation files('libs/MiPush_SDK_Client_3_6_2.jar')
混淆文件:proguard-android.txt
client sdk已經混淆過了,不需要再混淆。請使用keep命令保留client sdk的內容:
-keep class com.bqt.push.receiver.MiPushReceiver {*;}
#可以防止一個誤報的 warning 導致無法成功編譯,如果編譯使用的 Android 版本是 23。
-dontwarn com.xiaomi.push.**
AndroidManifest.xml
<!--====================== 推送SDK需要定義的權限 =====================-->
<!--極光-->
<!--小米-->
<permission
android:name="${applicationId}.permission.MIPUSH_RECEIVE"
android:protectionLevel="signature"/>
<uses-permission android:name="${applicationId}.permission.MIPUSH_RECEIVE"/>
<!--華為-->
<!--魅族-->
<!--========================= 小米推送需要注冊的組件 start =========================-->
<receiver
android:name=".receiver.MiPushReceiver"
android:exported="true"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE"/>
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED"/>
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.ERROR"/>
</intent-filter>
</receiver>
<receiver
android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver"
android:exported="true">
<intent-filter>
<action
android:name="android.net.conn.CONNECTIVITY_CHANGE"
tools:ignore="BatteryLife"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
<receiver
android:name="com.xiaomi.push.service.receivers.PingReceiver"
android:exported="false"
android:process=":pushservice">
<intent-filter>
<action android:name="com.xiaomi.push.PING_TIMER"/>
</intent-filter>
</receiver>
<service
android:name="com.xiaomi.push.service.XMJobService"
android:enabled="true"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":pushservice"/>
<service
android:name="com.xiaomi.push.service.XMPushService"
android:enabled="true"
android:process=":pushservice"/>
<service
android:name="com.xiaomi.mipush.sdk.PushMessageHandler"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedService"/>
<service
android:name="com.xiaomi.mipush.sdk.MessageHandleService"
android:enabled="true"/>
<!--========================= 小米推送需要注冊的組件 end =========================-->
2018-4-18