Android消息推送:手把手教你集成小米推送(附demo)
前言
在Android開(kāi)發(fā)中,消息推送功能的使用非常常見(jiàn)。

為了降低開(kāi)發(fā)成本,使用第三方推送是現(xiàn)今較為流行的解決方案。
今天,我將手把手教大家如何在你的應(yīng)用里集成小米推送
目錄

1. 官方Demo解析
首先,我們先對(duì)小米官方的推送Demo進(jìn)行解析。
請(qǐng)先到官網(wǎng)下載官方Demo和SDK說(shuō)明文檔
1.1 Demo概況

目錄說(shuō)明:
DemoApplication類
繼承自Application類,其作用主要是:設(shè)置App的ID & Key、注冊(cè)推送服務(wù)
DemoMessageReceiver類
繼承自BroadcastReceiver,用于接收推送消息并對(duì)這些消息進(jìn)行處理
MainActivity
實(shí)現(xiàn)界面按鈕處理 & 設(shè)置本地推送方案
TimeIntervalDialog
設(shè)置推送的時(shí)間間段
接下來(lái),我將對(duì)每個(gè)類進(jìn)行詳細(xì)分析
1.2 詳細(xì)分析
1.2.1 DemoApplication類
繼承自Application類,其作用主要是:
- 設(shè)置App的ID & Key
- 注冊(cè)推送服務(wù)
接下來(lái)我們通過(guò)代碼來(lái)看下這兩個(gè)功能如何實(shí)現(xiàn):
DemoApplication.Java
package com.xiaomi.mipushdemo;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Application;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.xiaomi.channel.commonutils.logger.LoggerInterface;
import com.xiaomi.mipush.sdk.Logger;
import com.xiaomi.mipush.sdk.MiPushClient;
import java.util.List;
public class DemoApplication extends Application {
// 使用自己APP的ID(官網(wǎng)注冊(cè)的)
private static final String APP_ID = "1000270";
// 使用自己APP的KEY(官網(wǎng)注冊(cè)的)
private static final String APP_KEY = "670100056270";
// 此TAG在adb logcat中檢索自己所需要的信息, 只需在命令行終端輸入 adb logcat | grep
// com.xiaomi.mipushdemo
public static final String TAG = "com.xiaomi.mipushdemo";
private static DemoHandler sHandler = null;
private static MainActivity sMainActivity = null;
//為了提高推送服務(wù)的注冊(cè)率,官方Demo建議在Application的onCreate中初始化推送服務(wù)
//你也可以根據(jù)需要,在其他地方初始化推送服務(wù)
@Override
public void onCreate() {
super.onCreate();
//判斷用戶是否已經(jīng)打開(kāi)App,詳細(xì)見(jiàn)下面方法定義
if (shouldInit()) {
//注冊(cè)推送服務(wù)
//注冊(cè)成功后會(huì)向DemoMessageReceiver發(fā)送廣播
// 可以從DemoMessageReceiver的onCommandResult方法中MiPushCommandMessage對(duì)象參數(shù)中獲取注冊(cè)信息
MiPushClient.registerPush(this, APP_ID, APP_KEY);
//參數(shù)說(shuō)明
//context:Android平臺(tái)上app的上下文,建議傳入當(dāng)前app的application context
//appID:在開(kāi)發(fā)者網(wǎng)站上注冊(cè)時(shí)生成的,MiPush推送服務(wù)頒發(fā)給app的唯一認(rèn)證標(biāo)識(shí)
//appKey:在開(kāi)發(fā)者網(wǎng)站上注冊(cè)時(shí)生成的,與appID相對(duì)應(yīng),用于驗(yàn)證appID是否合法
}
//下面是與測(cè)試相關(guān)的日志設(shè)置
LoggerInterface newLogger = new LoggerInterface() {
@Override
public void setTag(String tag) {
// ignore
}
@Override
public void log(String content, Throwable t) {
Log.d(TAG, content, t);
}
@Override
public void log(String content) {
Log.d(TAG, content);
}
};
Logger.setLogger(this, newLogger);
if (sHandler == null) {
sHandler = new DemoHandler(getApplicationContext());
}
}
//通過(guò)判斷手機(jī)里的所有進(jìn)程是否有這個(gè)App的進(jìn)程
//從而判斷該App是否有打開(kāi)
private boolean shouldInit() {
//通過(guò)ActivityManager我們可以獲得系統(tǒng)里正在運(yùn)行的activities
//包括進(jìn)程(Process)等、應(yīng)用程序/包、服務(wù)(Service)、任務(wù)(Task)信息。
ActivityManager am = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE));
List<RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
String mainProcessName = getPackageName();
//獲取本App的唯一標(biāo)識(shí)
int myPid = Process.myPid();
//利用一個(gè)增強(qiáng)for循環(huán)取出手機(jī)里的所有進(jìn)程
for (RunningAppProcessInfo info : processInfos) {
//通過(guò)比較進(jìn)程的唯一標(biāo)識(shí)和包名判斷進(jìn)程里是否存在該App
if (info.pid == myPid && mainProcessName.equals(info.processName)) {
return true;
}
}
return false;
}
public static DemoHandler getHandler() {
return sHandler;
}
public static void setMainActivity(MainActivity activity) {
sMainActivity = activity;
}
//通過(guò)設(shè)置Handler來(lái)設(shè)置提示文案
public static class DemoHandler extends Handler {
private Context context;
public DemoHandler(Context context) {
this.context = context;
}
@Override
public void handleMessage(Message msg) {
String s = (String) msg.obj;
if (sMainActivity != null) {
sMainActivity.refreshLogInfo();
}
if (!TextUtils.isEmpty(s)) {
Toast.makeText(context, s, Toast.LENGTH_LONG).show();
}
}
}
}
總結(jié):
步驟1:先判斷應(yīng)用App是否已開(kāi)啟 - 通過(guò)判斷系統(tǒng)里的進(jìn)程
通過(guò)靜態(tài)方法
public static void registerPush(Context context, String appID, String appKey)
進(jìn)行推送服務(wù)注冊(cè),詳細(xì)參數(shù)如下:

為了提高注冊(cè)率,最好在Application的onCreate中初始化推送服務(wù)
你也可以根據(jù)需要,在其他地方初始化推送服務(wù)
1.2.2 DemoMessageReceiver類
繼承自PushMessageReceiver(抽象類,繼承自BroadcastReceiver),其作用主要是:
- 接收推送消息
- 對(duì)推送消息進(jìn)行處理
DemoMessageReceiver.java
package com.xiaomi.mipushdemo;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import com.xiaomi.mipush.sdk.ErrorCode;
import com.xiaomi.mipush.sdk.MiPushClient;
import com.xiaomi.mipush.sdk.MiPushCommandMessage;
import com.xiaomi.mipush.sdk.MiPushMessage;
import com.xiaomi.mipush.sdk.PushMessageReceiver;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* 1、PushMessageReceiver 是個(gè)抽象類,該類繼承了 BroadcastReceiver。
* 2、需要將自定義的 DemoMessageReceiver 注冊(cè)在 AndroidManifest.xml
public class DemoMessageReceiver extends PushMessageReceiver {
private String mRegId;
private String mTopic;
private String mAlias;
private String mAccount;
private String mStartTime;
private String mEndTime;
//透?jìng)飨⒌竭_(dá)客戶端時(shí)調(diào)用
//作用:可通過(guò)參數(shù)message從而獲得透?jìng)飨?,具體請(qǐng)看官方SDK文檔
@Override
public void onReceivePassThroughMessage(Context context, MiPushMessage message) {
Log.v(DemoApplication.TAG,
"onReceivePassThroughMessage is called. " + message.toString());
String log = context.getString(R.string.recv_passthrough_message, message.getContent());
MainActivity.logList.add(0, getSimpleDate() + " " + log);
if (!TextUtils.isEmpty(message.getTopic())) {
mTopic = message.getTopic();
} else if (!TextUtils.isEmpty(message.getAlias())) {
mAlias = message.getAlias();
}
Message msg = Message.obtain();
msg.obj = log;
DemoApplication.getHandler().sendMessage(msg);
}
//通知消息到達(dá)客戶端時(shí)調(diào)用
//注:應(yīng)用在前臺(tái)時(shí)不彈出通知的通知消息到達(dá)客戶端時(shí)也會(huì)回調(diào)函數(shù)
//作用:通過(guò)參數(shù)message從而獲得通知消息,具體請(qǐng)看官方SDK文檔
@Override
public void onNotificationMessageArrived(Context context, MiPushMessage message) {
Log.v(DemoApplication.TAG,
"onNotificationMessageArrived is called. " + message.toString());
String log = context.getString(R.string.arrive_notification_message, message.getContent());
MainActivity.logList.add(0, getSimpleDate() + " " + log);
if (!TextUtils.isEmpty(message.getTopic())) {
mTopic = message.getTopic();
} else if (!TextUtils.isEmpty(message.getAlias())) {
mAlias = message.getAlias();
}
Message msg = Message.obtain();
msg.obj = log;
DemoApplication.getHandler().sendMessage(msg);
}
//用戶手動(dòng)點(diǎn)擊通知欄消息時(shí)調(diào)用
//注:應(yīng)用在前臺(tái)時(shí)不彈出通知的通知消息到達(dá)客戶端時(shí)也會(huì)回調(diào)函數(shù)
//作用:1. 通過(guò)參數(shù)message從而獲得通知消息,具體請(qǐng)看官方SDK文檔
//2. 設(shè)置用戶點(diǎn)擊消息后打開(kāi)應(yīng)用 or 網(wǎng)頁(yè) or 其他頁(yè)面
@Override
public void onNotificationMessageClicked(Context context, MiPushMessage message) {
Log.v(DemoApplication.TAG,
"onNotificationMessageClicked is called. " + message.toString());
String log = context.getString(R.string.click_notification_message, message.getContent());
MainActivity.logList.add(0, getSimpleDate() + " " + log);
if (!TextUtils.isEmpty(message.getTopic())) {
mTopic = message.getTopic();
} else if (!TextUtils.isEmpty(message.getAlias())) {
mAlias = message.getAlias();
}
Message msg = Message.obtain();
if (message.isNotified()) {
msg.obj = log;
}
DemoApplication.getHandler().sendMessage(msg);
}
//用來(lái)接收客戶端向服務(wù)器發(fā)送命令后的響應(yīng)結(jié)果。
@Override
public void onCommandResult(Context context, MiPushCommandMessage message) {
Log.v(DemoApplication.TAG,
"onCommandResult is called. " + message.toString());
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);
String log;
if (MiPushClient.COMMAND_REGISTER.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mRegId = cmdArg1;
log = context.getString(R.string.register_success);
} else {
log = context.getString(R.string.register_fail);
}
} else if (MiPushClient.COMMAND_SET_ALIAS.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mAlias = cmdArg1;
log = context.getString(R.string.set_alias_success, mAlias);
} else {
log = context.getString(R.string.set_alias_fail, message.getReason());
}
} else if (MiPushClient.COMMAND_UNSET_ALIAS.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mAlias = cmdArg1;
log = context.getString(R.string.unset_alias_success, mAlias);
} else {
log = context.getString(R.string.unset_alias_fail, message.getReason());
}
} else if (MiPushClient.COMMAND_SET_ACCOUNT.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mAccount = cmdArg1;
log = context.getString(R.string.set_account_success, mAccount);
} else {
log = context.getString(R.string.set_account_fail, message.getReason());
}
} else if (MiPushClient.COMMAND_UNSET_ACCOUNT.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mAccount = cmdArg1;
log = context.getString(R.string.unset_account_success, mAccount);
} else {
log = context.getString(R.string.unset_account_fail, message.getReason());
}
} else if (MiPushClient.COMMAND_SUBSCRIBE_TOPIC.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mTopic = cmdArg1;
log = context.getString(R.string.subscribe_topic_success, mTopic);
} else {
log = context.getString(R.string.subscribe_topic_fail, message.getReason());
}
} else if (MiPushClient.COMMAND_UNSUBSCRIBE_TOPIC.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mTopic = cmdArg1;
log = context.getString(R.string.unsubscribe_topic_success, mTopic);
} else {
log = context.getString(R.string.unsubscribe_topic_fail, message.getReason());
}
} else if (MiPushClient.COMMAND_SET_ACCEPT_TIME.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mStartTime = cmdArg1;
mEndTime = cmdArg2;
log = context.getString(R.string.set_accept_time_success, mStartTime, mEndTime);
} else {
log = context.getString(R.string.set_accept_time_fail, message.getReason());
}
} else {
log = message.getReason();
}
MainActivity.logList.add(0, getSimpleDate() + " " + log);
Message msg = Message.obtain();
msg.obj = log;
DemoApplication.getHandler().sendMessage(msg);
}
//用于接收客戶端向服務(wù)器發(fā)送注冊(cè)命令后的響應(yīng)結(jié)果。
@Override
public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {
Log.v(DemoApplication.TAG,
"onReceiveRegisterResult is called. " + message.toString());
String command = message.getCommand();
List<String> arguments = message.getCommandArguments();
String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
String log;
if (MiPushClient.COMMAND_REGISTER.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
mRegId = cmdArg1;
//打印日志:注冊(cè)成功
log = context.getString(R.string.register_success);
} else {
//打印日志:注冊(cè)失敗
log = context.getString(R.string.register_fail);
}
} else {
log = message.getReason();
}
Message msg = Message.obtain();
msg.obj = log;
DemoApplication.getHandler().sendMessage(msg);
}
@SuppressLint("SimpleDateFormat")
private static String getSimpleDate() {
return new SimpleDateFormat("MM-dd hh:mm:ss").format(new Date());
}
}
總結(jié)
根據(jù)需要復(fù)寫(xiě)PushMessageReceiver里對(duì)消息的相關(guān)處理方法,以下是相關(guān)方法的詳情:

關(guān)于onCommandResult(Context context,MiPushCommandMessage message)
a. 作用:當(dāng)客戶端向服務(wù)器發(fā)送注冊(cè)push、設(shè)置alias、取消注冊(cè)alias、訂閱topic、取消訂閱topic等等命令后,從服務(wù)器返回結(jié)果。
b. 參數(shù)說(shuō)明:

1.2.3 MainActivity
用于給用戶設(shè)置標(biāo)識(shí),如別名、標(biāo)簽、賬號(hào)等等
MainActivity.java
public class MainActivity extends Activity {
public static List<String> logList = new CopyOnWriteArrayList<String>();
private TextView mLogView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DemoApplication.setMainActivity(this);
mLogView = (TextView) findViewById(R.id.log);
// 設(shè)置別名
findViewById(R.id.set_alias).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final EditText editText = new EditText(MainActivity.this);
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.set_alias)
.setView(editText)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String alias = editText.getText().toString();
//調(diào)用靜態(tài)方法進(jìn)行設(shè)置 MiPushClient.setAlias(MainActivity.this, alias, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
});
// 撤銷別名
findViewById(R.id.unset_alias).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final EditText editText = new EditText(MainActivity.this);
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.unset_alias)
.setView(editText)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String alias = editText.getText().toString();
//調(diào)用靜態(tài)方法進(jìn)行設(shè)置 MiPushClient.unsetAlias(MainActivity.this, alias, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
});
// 設(shè)置帳號(hào)
findViewById(R.id.set_account).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final EditText editText = new EditText(MainActivity.this);
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.set_account)
.setView(editText)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String account = editText.getText().toString();
//調(diào)用靜態(tài)方法進(jìn)行設(shè)置 MiPushClient.setUserAccount(MainActivity.this, account, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
});
// 撤銷帳號(hào)
findViewById(R.id.unset_account).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final EditText editText = new EditText(MainActivity.this);
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.unset_account)
.setView(editText)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String account = editText.getText().toString();
//調(diào)用靜態(tài)方法進(jìn)行設(shè)置 MiPushClient.unsetUserAccount(MainActivity.this, account, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
});
// 設(shè)置標(biāo)簽
findViewById(R.id.subscribe_topic).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final EditText editText = new EditText(MainActivity.this);
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.subscribe_topic)
.setView(editText)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String topic = editText.getText().toString();
//調(diào)用靜態(tài)方法進(jìn)行設(shè)置 MiPushClient.subscribe(MainActivity.this, topic, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
});
// 撤銷標(biāo)簽
findViewById(R.id.unsubscribe_topic).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final EditText editText = new EditText(MainActivity.this);
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.unsubscribe_topic)
.setView(editText)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String topic = editText.getText().toString();
//調(diào)用靜態(tài)方法進(jìn)行設(shè)置 MiPushClient.unsubscribe(MainActivity.this, topic, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
});
// 設(shè)置接收消息時(shí)間
findViewById(R.id.set_accept_time).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new TimeIntervalDialog(MainActivity.this, new TimeIntervalInterface() {
@Override
public void apply(int startHour, int startMin, int endHour,
int endMin) {
//調(diào)用靜態(tài)方法進(jìn)行設(shè)置
MiPushClient.setAcceptTime(MainActivity.this, startHour, startMin, endHour, endMin, null);
}
@Override
public void cancel() {
//ignore
}
})
.show();
}
});
// 暫停推送
findViewById(R.id.pause_push).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
MiPushClient.pausePush(MainActivity.this, null);
}
});
findViewById(R.id.resume_push).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//調(diào)用靜態(tài)方法進(jìn)行設(shè)置
MiPushClient.resumePush(MainActivity.this, null);
}
});
}
@Override
protected void onResume() {
super.onResume();
refreshLogInfo();
}
@Override
protected void onDestroy() {
super.onDestroy();
DemoApplication.setMainActivity(null);
}
public void refreshLogInfo() {
String AllLog = "";
for (String log : logList) {
AllLog = AllLog + log + "\n\n";
}
mLogView.setText(AllLog);
}
}
總結(jié):
根據(jù)需求對(duì)不同用戶設(shè)置不同的推送標(biāo)識(shí),如別名、標(biāo)簽等等。
a. 別名(Alias)
1、開(kāi)發(fā)者可以為指定用戶設(shè)置別名,然后給這個(gè)別名推送消息,
效果等同于給RegId推送消息,Alias是除Regid(自動(dòng)生成的)和UserAccount之外的第三個(gè)用戶標(biāo)識(shí)
2、開(kāi)發(fā)者可以取消指定用戶的某個(gè)別名,服務(wù)器就不會(huì)給這個(gè)別名推送消息了。
//設(shè)置別名 MiPushClient.setAlias(Context context, String alias, String category); //撤銷別名 MiPushClient.unsetAlias(Context context, String alias, String category); //參數(shù)說(shuō)明 //context:Android平臺(tái)上app的上下文,建議傳入當(dāng)前app的application context //alias:為指定用戶設(shè)置別名 / 為指定用戶取消別名 //category:擴(kuò)展參數(shù),暫時(shí)沒(méi)有用途,直接填null //獲取該客戶端所有的別名 public static List<String> getAllAlias(final Context context)
b. 用戶賬號(hào)(UserAccoun)
- 開(kāi)發(fā)者可以為指定用戶設(shè)置userAccount
- 開(kāi)發(fā)者可以取消指定用戶的某個(gè)userAccount,服務(wù)器就不會(huì)給這個(gè)userAccount推送消息了
//設(shè)置 MiPushClient.setUserAccount(final Context context, final String userAccount, String category) //撤銷 MiPushClient.unsetUserAccount(final Context context, final String userAccount, String category) //參數(shù)說(shuō)明 //context:Android平臺(tái)上app的上下文,建議傳入當(dāng)前app的application context //userAccount:為指定用戶設(shè)置userAccount / 為指定用戶取消userAccount //category:擴(kuò)展參數(shù),暫時(shí)沒(méi)有用途,直接填null //獲取該客戶端所有設(shè)置的賬號(hào) public static List<String> getAllUserAccount(final Context context)
c. 標(biāo)簽(Topic)
- 開(kāi)發(fā)者可以結(jié)合自己的業(yè)務(wù)特征,給用戶打上不同的標(biāo)簽。
- 消息推送時(shí),開(kāi)發(fā)者可以結(jié)合每條消息的內(nèi)容和目標(biāo)用戶,為每條消息選擇對(duì)應(yīng)的標(biāo)簽,為開(kāi)發(fā)者可以根據(jù)訂閱的主題實(shí)現(xiàn)分組群發(fā),從而進(jìn)行消息的精準(zhǔn)推送
//設(shè)置標(biāo)簽 MiPushClient.subscribe(Context context, String topic, String category); //撤銷標(biāo)簽 MiPushClient.unsubscribe(Context context, String topic, String category); //參數(shù)說(shuō)明 //context:Android平臺(tái)上app的上下文,建議傳入當(dāng)前app的application context //topic:為指定用戶設(shè)置設(shè)置訂閱的主題 / 為指定用戶取消訂閱的主題 //category:擴(kuò)展參數(shù),暫時(shí)沒(méi)有用途,直接填null //獲取該客戶端所有的標(biāo)簽 public static List<String> getAllTopic(final Context context);
TimeIntervalDialog
作用:用于設(shè)置推送的時(shí)間-開(kāi)始時(shí)間+暫停時(shí)間
package com.xiaomi.mipushdemo;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TimePicker;
import android.widget.TimePicker.OnTimeChangedListener;
//繼承OnTimeChangedListener接口
public class TimeIntervalDialog extends Dialog implements OnTimeChangedListener {
private TimeIntervalInterface mTimeIntervalInterface;
private Context mContext;
private TimePicker mStartTimePicker, mEndTimePicker;
private int mStartHour, mStartMinute, mEndHour, mEndMinute;
private Button.OnClickListener clickListener = new Button.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.apply:
dismiss();
//設(shè)置時(shí)間參數(shù)
mTimeIntervalInterface.apply(mStartHour, mStartMinute, mEndHour, mEndMinute);
break;
case R.id.cancel:
dismiss();
mTimeIntervalInterface.cancel();
break;
default:
break;
}
}
};
public TimeIntervalDialog(Context context, TimeIntervalInterface timeIntervalInterface,
int startHour, int startMinute, int endHour, int endMinute) {
super(context);
mContext = context;
this.mTimeIntervalInterface = timeIntervalInterface;
this.mStartHour = startHour;
this.mStartMinute = startMinute;
this.mEndHour = endHour;
this.mEndMinute = endMinute;
}
public TimeIntervalDialog(Context context, TimeIntervalInterface timeIntervalInterface) {
this(context, timeIntervalInterface, 0, 0, 23, 59);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.set_time_dialog);
setCancelable(true);
setTitle(mContext.getString(R.string.set_accept_time));
mStartTimePicker = (TimePicker) findViewById(R.id.startTimePicker);
mStartTimePicker.setIs24HourView(true);
mStartTimePicker.setCurrentHour(mStartHour);
mStartTimePicker.setCurrentMinute(mStartMinute);
mStartTimePicker.setOnTimeChangedListener(this);
mEndTimePicker = (TimePicker) findViewById(R.id.endTimePicker);
mEndTimePicker.setIs24HourView(true);
mEndTimePicker.setCurrentHour(mEndHour);
mEndTimePicker.setCurrentMinute(mEndMinute);
mEndTimePicker.setOnTimeChangedListener(this);
Button applyBtn = (Button) findViewById(R.id.apply);
applyBtn.setOnClickListener(clickListener);
Button cancelBtn = (Button) findViewById(R.id.cancel);
cancelBtn.setOnClickListener(clickListener);
}
@Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
if (view == mStartTimePicker) {
mStartHour = hourOfDay;
mStartMinute = minute;
} else if (view == mEndTimePicker) {
mEndHour = hourOfDay;
mEndMinute = minute;
}
}
interface TimeIntervalInterface {
void apply(int startHour, int startMin, int endHour, int endMin);
void cancel();
}
}
總結(jié)
- 使用一個(gè)繼承了Dialog類的TimeIntervalDialog類進(jìn)行推送時(shí)間的配置
- 可進(jìn)行的配置:設(shè)置推送時(shí)間(開(kāi)始 & 結(jié)束)、暫停推送時(shí)間、恢復(fù)推送時(shí)間
//設(shè)置推送時(shí)間(開(kāi)始 & 結(jié)束) MiPushClient.setAcceptTime(Context context, int startHour, int startMin, int endHour, int endMin, String category) //設(shè)置暫停推送時(shí)間、恢復(fù)推送時(shí)間 pausePush(Context context, String category)`和`resumePush(Context context, String category) //參數(shù)說(shuō)明 //context:Android平臺(tái)上app的上下文,建議傳入當(dāng)前app的application context //startHour:接收時(shí)段開(kāi)始時(shí)間的小時(shí) //startMin :接收時(shí)段開(kāi)始時(shí)間的分鐘 //endHour:接收時(shí)段結(jié)束時(shí)間的小時(shí) //endMin:接收時(shí)段結(jié)束時(shí)間的分鐘 //category:擴(kuò)展參數(shù),暫時(shí)沒(méi)有用途,直接填null
AndroidManifest文件的配置
//小米推送支持最低的Android版本是2.2
<uses-sdk android:minSdkVersion="8"/>
//設(shè)置一系列權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.VIBRATE" />
//這里com.xiaomi.mipushdemo改成自身app的包名
<permission android:name="com.xiaomi.mipushdemo.permission.MIPUSH_RECEIVE" android:protectionLevel="signature" />
//這里com.xiaomi.mipushdemo改成自身app的包名
<uses-permission android:name="com.xiaomi.mipushdemo.permission.MIPUSH_RECEIVE" />
//注冊(cè)廣播BroadcastReceiver & Service
//都是靜態(tài)注冊(cè),因?yàn)橐L(zhǎng)期處在后臺(tái)運(yùn)行
//注:共是3個(gè)廣播接收器和4個(gè)服務(wù),其中包括繼承了PushMessageReceiver的DemoMessageReceiver
//4個(gè)后臺(tái)服務(wù)
<service
android:enabled="true"
android:process=":pushservice"
android:name="com.xiaomi.push.service.XMPushService"/>
//此service必須在3.0.1版本以后(包括3.0.1版本)加入
<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必須在2.2.5版本以后(包括2.2.5版本)加入
<service
android:enabled="true"
android:exported="true"
android:name="com.xiaomi.mipush.sdk.PushMessageHandler" />
<service android:enabled="true"
android:name="com.xiaomi.mipush.sdk.MessageHandleService" />
//3個(gè)廣播
<receiver
android:exported="true"
android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:exported="false"
android:process=":pushservice"
android:name="com.xiaomi.push.service.receivers.PingReceiver" >
<intent-filter>
<action android:name="com.xiaomi.push.PING_TIMER" />
</intent-filter>
</receiver>
//繼承了PushMessageReceiver的DemoMessageReceiver的廣播注冊(cè)
<receiver
android:name="com.xiaomi.mipushdemo.DemoMessageReceiver"
android:exported="true">
<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>
2. 集成小米推送步驟匯總
- 步驟1:在小米推送平臺(tái)進(jìn)行相關(guān)注冊(cè)開(kāi)發(fā)者賬號(hào),并進(jìn)行應(yīng)用的注冊(cè):應(yīng)用包名,AppID和AppKey
- 步驟2:將小米推送的SDK包加入庫(kù)
- 步驟3:在應(yīng)用內(nèi)初始化小米推送服務(wù)
- 步驟4:繼承PushMessageReceiver,并復(fù)寫(xiě)相關(guān)推送消息的方法
- 步驟5:在AndroidManifest文件里面配置好權(quán)限、注冊(cè)Service和BroadcastReceiver
- 在Android6.0里面的權(quán)限需要?jiǎng)討B(tài)獲取
- 步驟6:根據(jù)需要設(shè)置一系列的推送設(shè)置,如用戶別名、標(biāo)簽等等
接下來(lái),我們來(lái)按照上面的步驟,一步步來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)易的小米推送Demo
3. 實(shí)例解析
步驟1:在小米推送平臺(tái)進(jìn)行相關(guān)注冊(cè)開(kāi)發(fā)者賬號(hào),并進(jìn)行應(yīng)用的注冊(cè):應(yīng)用包名,AppID和AppKey
注意,填入的包名要跟你的應(yīng)用App的包名是一致的


步驟2:將小米推送的SDK包加入到你應(yīng)用的庫(kù)里

步驟3:在應(yīng)用內(nèi)初始化小米推送服務(wù)
為了提高推送服務(wù)的注冊(cè)率,我選擇在Application的onCreate中初始化推送服務(wù) *BaseActivity.java*
package scut.carson_ho.demo_mipush;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.os.Process;
import com.xiaomi.mipush.sdk.MiPushClient;
import java.util.List;
/**
* Created by Carson_Ho on 16/10/26.
*/
//主要要繼承Application
public class BaseActivity extends Application {
// 使用自己APP的ID(官網(wǎng)注冊(cè)的)
private static final String APP_ID = "2882303761517520369";
// 使用自己APP的Key(官網(wǎng)注冊(cè)的)
private static final String APP_KEY = "5401752085369";
//為了提高推送服務(wù)的注冊(cè)率,我建議在Application的onCreate中初始化推送服務(wù)
//你也可以根據(jù)需要,在其他地方初始化推送服務(wù)
@Override
public void onCreate() {
super.onCreate();
if (shouldInit()) {
//注冊(cè)推送服務(wù)
//注冊(cè)成功后會(huì)向DemoMessageReceiver發(fā)送廣播
// 可以從DemoMessageReceiver的onCommandResult方法中MiPushCommandMessage對(duì)象參數(shù)中獲取注冊(cè)信息
MiPushClient.registerPush(this, APP_ID, APP_KEY);
}
}
//通過(guò)判斷手機(jī)里的所有進(jìn)程是否有這個(gè)App的進(jìn)程
//從而判斷該App是否有打開(kāi)
private boolean shouldInit() {
//通過(guò)ActivityManager我們可以獲得系統(tǒng)里正在運(yùn)行的activities
//包括進(jìn)程(Process)等、應(yīng)用程序/包、服務(wù)(Service)、任務(wù)(Task)信息。
ActivityManager am = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE));
List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
String mainProcessName = getPackageName();
//獲取本App的唯一標(biāo)識(shí)
int myPid = Process.myPid();
//利用一個(gè)增強(qiáng)for循環(huán)取出手機(jī)里的所有進(jìn)程
for (ActivityManager.RunningAppProcessInfo info : processInfos) {
//通過(guò)比較進(jìn)程的唯一標(biāo)識(shí)和包名判斷進(jìn)程里是否存在該App
if (info.pid == myPid && mainProcessName.equals(info.processName)) {
return true;
}
}
return false;
}
}
注意要在Android.manifest.xml里的application里加入
android:name=".BaseActivity"
這樣在應(yīng)用初始化時(shí)是第一個(gè)加載BaseActivity.java類文件的
如下圖:

步驟4:設(shè)置子類繼承PushMessageReceiver,并復(fù)寫(xiě)相關(guān)推送消息的方法
Mipush_Broadcast.java
package scut.carson_ho.demo_mipush;
import android.content.Context;
import com.xiaomi.mipush.sdk.ErrorCode;
import com.xiaomi.mipush.sdk.MiPushClient;
import com.xiaomi.mipush.sdk.MiPushCommandMessage;
import com.xiaomi.mipush.sdk.MiPushMessage;
import com.xiaomi.mipush.sdk.PushMessageReceiver;
/**
* Created by Carson_Ho on 16/10/26.
*/
public class Mipush_Broadcast extends PushMessageReceiver {
//透?jìng)飨⒌竭_(dá)客戶端時(shí)調(diào)用
//作用:可通過(guò)參數(shù)message從而獲得透?jìng)飨?,具體請(qǐng)看官方SDK文檔
@Override
public void onReceivePassThroughMessage(Context context, MiPushMessage message) {
//打印消息方便測(cè)試
System.out.println("透?jìng)飨⒌竭_(dá)了");
System.out.println("透?jìng)飨⑹?+message.toString());
}
//通知消息到達(dá)客戶端時(shí)調(diào)用
//注:應(yīng)用在前臺(tái)時(shí)不彈出通知的通知消息到達(dá)客戶端時(shí)也會(huì)回調(diào)函數(shù)
//作用:通過(guò)參數(shù)message從而獲得通知消息,具體請(qǐng)看官方SDK文檔
@Override
public void onNotificationMessageArrived(Context context, MiPushMessage message) {
//打印消息方便測(cè)試
System.out.println("通知消息到達(dá)了");
System.out.println("通知消息是"+message.toString());
}
//用戶手動(dòng)點(diǎn)擊通知欄消息時(shí)調(diào)用
//注:應(yīng)用在前臺(tái)時(shí)不彈出通知的通知消息到達(dá)客戶端時(shí)也會(huì)回調(diào)函數(shù)
//作用:1. 通過(guò)參數(shù)message從而獲得通知消息,具體請(qǐng)看官方SDK文檔
//2. 設(shè)置用戶點(diǎn)擊消息后打開(kāi)應(yīng)用 or 網(wǎng)頁(yè) or 其他頁(yè)面
@Override
public void onNotificationMessageClicked(Context context, MiPushMessage message) {
//打印消息方便測(cè)試
System.out.println("用戶點(diǎn)擊了通知消息");
System.out.println("通知消息是" + message.toString());
System.out.println("點(diǎn)擊后,會(huì)進(jìn)入應(yīng)用" );
}
//用來(lái)接收客戶端向服務(wù)器發(fā)送命令后的響應(yīng)結(jié)果。
@Override
public void onCommandResult(Context context, MiPushCommandMessage message) {
String command = message.getCommand();
System.out.println(command );
if (MiPushClient.COMMAND_REGISTER.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
//打印信息便于測(cè)試注冊(cè)成功與否
System.out.println("注冊(cè)成功");
} else {
System.out.println("注冊(cè)失敗");
}
}
}
//用于接收客戶端向服務(wù)器發(fā)送注冊(cè)命令后的響應(yīng)結(jié)果。
@Override
public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {
String command = message.getCommand();
System.out.println(command );
if (MiPushClient.COMMAND_REGISTER.equals(command)) {
if (message.getResultCode() == ErrorCode.SUCCESS) {
//打印日志:注冊(cè)成功
System.out.println("注冊(cè)成功");
} else {
//打印日志:注冊(cè)失敗
System.out.println("注冊(cè)失敗");
}
} else {
System.out.println("其他情況"+message.getReason());
}
}
}
步驟5:在AndroidManifest文件里面配置好權(quán)限、注冊(cè)Service和BroadcastReceiver
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="scut.carson_ho.demo_mipush">
//相關(guān)權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.VIBRATE" />
//注意這里.permission.MIPUSH_RECEIVE是自身app的包名
<permission android:name="scut.carson_ho.demo_mipush.permission.MIPUSH_RECEIVE" android:protectionLevel="signature" />
//注意這里.permission.MIPUSH_RECEIVE是自身app的包名
<uses-permission android:name="scut.carson_ho.demo_mipush.permission.MIPUSH_RECEIVE" />
//注意要初始化BaseActivity.java類
<application
android:name=".BaseActivity"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
//注冊(cè)廣播BroadcastReceiver和Service
//都是靜態(tài)注冊(cè),因?yàn)橐L(zhǎng)期處在后臺(tái)運(yùn)行
//注:共是3個(gè)廣播接收器和4個(gè)服務(wù),其中包括繼承了PushMessageReceiver的DemoMessageReceiver
//4個(gè)后臺(tái)服務(wù)
<service
android:enabled="true"
android:process=":pushservice"
android:name="com.xiaomi.push.service.XMPushService"/>
//此service必須在3.0.1版本以后(包括3.0.1版本)加入
<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必須在2.2.5版本以后(包括2.2.5版本)加入
<service
android:enabled="true"
android:exported="true"
android:name="com.xiaomi.mipush.sdk.PushMessageHandler" />
<service android:enabled="true"
android:name="com.xiaomi.mipush.sdk.MessageHandleService" />
//3個(gè)廣播
<receiver
android:exported="true"
android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:exported="false"
android:process=":pushservice"
android:name="com.xiaomi.push.service.receivers.PingReceiver" >
<intent-filter>
<action android:name="com.xiaomi.push.PING_TIMER" />
</intent-filter>
</receiver>
//繼承了PushMessageReceiver的DemoMessageReceiver的廣播注冊(cè)
<receiver
android:name=".Mipush_Broadcast"
android:exported="true">
<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>
</application>
</manifest>
步驟6:根據(jù)需要設(shè)置一系列的推送設(shè)置,如用戶別名、標(biāo)簽等等
- 此處是簡(jiǎn)單Demo,所以不作過(guò)多的設(shè)置
- 更多設(shè)置請(qǐng)回看上方官方Demo解析
運(yùn)行結(jié)果

好了,客戶端的代碼寫(xiě)好后,可以去小米官網(wǎng)測(cè)試一下消息推送了
步驟1:在小米官網(wǎng)的消息推送里選擇你創(chuàng)建的應(yīng)用,然后點(diǎn)擊“推送工具”

步驟2:設(shè)置推送消息的相關(guān)信息
可進(jìn)行的配置非常全面,基本上能滿足推送的需求

推送的結(jié)果



4. Demo下載地址 demo
5. 關(guān)于對(duì)小米推送的思考(問(wèn)題)
上述說(shuō)的小米推送看似簡(jiǎn)單:初始化推送服務(wù) + 相關(guān)推送設(shè)置。但是,好的代碼不僅能在正常情況下工作,還應(yīng)該充分考慮失敗情況。那么,有什么樣的失敗情況需要我們考慮呢?
- 背景:在這個(gè)初始化推送服務(wù)的過(guò)程中,是需要聯(lián)系小米推送的服務(wù)器來(lái)申請(qǐng)reg id(即推送token)。
- 沖突:初始化過(guò)程可能失?。壕W(wǎng)絡(luò)問(wèn)題(沒(méi)網(wǎng)or網(wǎng)絡(luò)信號(hào)弱)、服務(wù)器問(wèn)題導(dǎo)致初始化失敗。那么,當(dāng)失敗以后,該什么時(shí)候再次進(jìn)行初始化呢?
- 解決方案:在初始化失敗的情況下提供重試機(jī)制,直到初始化成功(可以通過(guò)檢測(cè)是否已經(jīng)拿到推送token來(lái)確定),問(wèn)題解決的邏輯如下:

總結(jié)
全面考慮到所有異常問(wèn)題并恰當(dāng)?shù)剡M(jìn)行處理才能真正體現(xiàn)程序猿的功力,希望大家做擼代碼的時(shí)候不要只做代碼的搬運(yùn)工,純粹寫(xiě)代碼并不會(huì)讓你成長(zhǎng),關(guān)鍵在于思考。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android Handler,Message,MessageQueue,Loper源碼解析詳解
這篇文章主要介紹了Android Handler,Message,MessageQueue,Loper源碼解析詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
android app跳轉(zhuǎn)應(yīng)用商店實(shí)現(xiàn)步驟
這篇文章主要為大家介紹了android app跳轉(zhuǎn)應(yīng)用商店實(shí)現(xiàn)步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Android自定義View中attrs.xml的實(shí)例詳解
這篇文章主要介紹了Android自定義View中attrs.xml的實(shí)例詳解的相關(guān)資料,在自定義View首先對(duì)attrs.xml進(jìn)行布局的實(shí)現(xiàn)及屬性的應(yīng)用,需要的朋友可以參考下2017-07-07
Android?Flutter制作一個(gè)修改組件屬性的動(dòng)畫(huà)
flutter為我們提供了一個(gè)AnimationController來(lái)對(duì)動(dòng)畫(huà)進(jìn)行詳盡的控制,不過(guò)直接是用AnimationController是比較復(fù)雜的,如果只是對(duì)一個(gè)widget的屬性進(jìn)行修改,可以做成動(dòng)畫(huà)嗎,本文就來(lái)探討一下2023-05-05
android 實(shí)現(xiàn)控件左右或上下抖動(dòng)教程
這篇文章主要介紹了android 實(shí)現(xiàn)控件左右或上下抖動(dòng)教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
IDEA打包jar-解決找不到或無(wú)法加載主類 main的問(wèn)題
這篇文章主要介紹了IDEA打包jar-解決找不到或無(wú)法加載主類 main的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Android實(shí)現(xiàn)布局動(dòng)畫(huà)和共享動(dòng)畫(huà)的結(jié)合效果
今天給大家?guī)?lái)能夠提升用戶體驗(yàn)感的交互動(dòng)畫(huà),使用起來(lái)非常簡(jiǎn)單,體驗(yàn)效果非常贊,其中僅使用到布局動(dòng)畫(huà)和共享動(dòng)畫(huà),文章通過(guò)代碼示例介紹的非常詳細(xì),感興趣的同學(xué)可以自己動(dòng)手試一試2023-09-09
Android 實(shí)現(xiàn)滑動(dòng)的六種方式
這篇文章主要給大家分享的是Android 實(shí)現(xiàn)滑動(dòng)的六種方式,分別是layout、scrollBy、offsetLeftAndRight offsetTopAndButton、LayoutParams、Scroller、平移動(dòng)畫(huà),需要的朋友可以參考一下下面文章的具體內(nèi)容2021-11-11

