想必對(duì)于Android開發(fā)者來(lái)說(shuō),對(duì)Service一定不陌生了阵面,作為大名鼎鼎的四大組件之一的service,在Android中有著不可替代的作用,它不像Activity那么光鮮亮麗才沧,一般都是默默躲在后臺(tái)執(zhí)行著一些“見不得人的”任務(wù),比如下載文件绍刮,音樂播放等等糜工,即使退出應(yīng)用了,它還是很頑強(qiáng)的在后臺(tái)運(yùn)行著录淡,雖然隨著android版本的不斷提高捌木,安全性的要求也越來(lái)越高,Service的一些黑科技也變得越來(lái)越難嫉戚。
最近在學(xué)習(xí)Service刨裆,做個(gè)記錄,希望能給您一些幫助彬檀,我們都知道service有兩種啟動(dòng)方式帆啃,分別是startService和bindService,它們有各自的生命周期方法窍帝,盜張官網(wǎng)的圖:
下面分別來(lái)看下它的使用吧努潘,我們先創(chuàng)建一個(gè)Service:
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e("wangkeke","------onBind");
return new MyBinder();
}
public interface MyIBinder{
void showGetMyService();
}
public class MyBinder extends Binder implements MyIBinder{
public void stopService(ServiceConnection serviceConnection){
unbindService(serviceConnection);
}
@Override
public void showGetMyService() {
Log.e("wangkeke","------獲取遠(yuǎn)程服務(wù)");
}
}
@Override
public void onCreate() {
super.onCreate();
Log.e("wangkeke","------onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("wangkeke","------onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.e("wangkeke","------onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("wangkeke","------onDestroy");
}
}
我們簡(jiǎn)單定義了一個(gè)MyService,重寫了幾個(gè)比較重要的方法,別忘了在AndroidManifest.xml里面注冊(cè):
<service android:name=".MyService"/>
1.startService
然后在Activity里我們先通過(guò)startService來(lái)啟動(dòng)它:
Intent intent = new Intent(MainActivity.this,MyService.class);
startService(intent);
很簡(jiǎn)單傳遞給它一個(gè)intent疯坤,指定要啟動(dòng)的Service就可以了报慕,運(yùn)行看看結(jié)果:
執(zhí)行了onCreate,onStartCommand压怠,那么如果我們多次startService呢眠冈?看看效果:
可以看出只有第一次執(zhí)行才會(huì)調(diào)用onCreate,之后只會(huì)調(diào)用onStartCommand方法菌瘫,啟動(dòng)是成功了蜗顽,但我們?cè)撊绾瓮V筍ervcie呢?一種方式是在自定義的Service中使用stopSelf()停止自己雨让,另一種是在外部通過(guò)stopService(intent)的方式停止服務(wù)雇盖。
現(xiàn)在我們調(diào)用如下停止服務(wù)的代碼:
Intent intent = new Intent(MainActivity.this,MyService.class);
stopService(intent);
看控制臺(tái)打印日志:
停止服務(wù)只會(huì)調(diào)用onDestory方法,多次調(diào)用stopService時(shí)栖忠,onDestory只會(huì)調(diào)用一次刊懈,這個(gè)也很好理解,比較服務(wù)都不在了娃闲,調(diào)用也就無(wú)效了虚汛。
startService適用于執(zhí)行后臺(tái)任務(wù),但是要注意service默認(rèn)是在主線程執(zhí)行的皇帮,執(zhí)行耗時(shí)操作的話請(qǐng)務(wù)必開啟子線程去處理卷哩。
2.bindService
下面我們換種方式啟動(dòng)Service,通過(guò)bindService的方式属拾,bindService方法需要三個(gè)參數(shù):
@Override
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
return mBase.bindService(service, conn, flags);
}
service就是我們的intent将谊,ServiceConnection是個(gè)接口,當(dāng)我們bindservice之后渐白,AMS會(huì)回調(diào)ServiceConnection接口對(duì)象的onServiceConnected()方法尊浓,onServiceConnected方法會(huì)把遠(yuǎn)端service的代理binder傳遞過(guò)來(lái),這樣就可以通過(guò)這個(gè)代理binder跨進(jìn)程調(diào)用service中的方法了纯衍。
我們先創(chuàng)建一個(gè)ServiceConnection對(duì)象:
public ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e("wangkeke","-------onServiceConnected");
serviceIsConnected = true;
MyService.MyBinder myBinder = (MyService.MyBinder) service;
myBinder.showGetMyService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("wangkeke","-------onServiceDisconnected");
serviceIsConnected = false;
}
};
然后通過(guò)bindService啟動(dòng)它:
Intent intent = new Intent(MainActivity.this,MyService.class);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
運(yùn)行結(jié)果如下:
依次調(diào)用了onCreate栋齿,onBind,之后遠(yuǎn)程服務(wù)連接成功襟诸,調(diào)用了onServiceConnected瓦堵,可以看到所有的回調(diào)都在主線程,當(dāng)服務(wù)連接成功后歌亲,通過(guò)onServiceConnected拿到IBinder對(duì)象菇用,就可以調(diào)用遠(yuǎn)程service的方法了。
bindService的第三個(gè)參數(shù)的含義:bindservice在建立連接時(shí)陷揪,如果發(fā)現(xiàn)service還沒啟動(dòng)惋鸥,會(huì)根據(jù)flag是否設(shè)置BIND_AUTO_CREATE杂穷,決定是否啟動(dòng)這個(gè)service。
那么綁定后的service如何解綁呢卦绣,通過(guò)unbindService方法耐量,傳入綁定service時(shí)所創(chuàng)建的ServiceConnection對(duì)象即可:
unbindService(conn);
生命周期打印如下:
ServiceConnection接口的onServiceDisconnected()方法并不會(huì)在unbindService()操作斷開邏輯連接時(shí)執(zhí)行。而是在遠(yuǎn)端service進(jìn)程終止時(shí)迎卤,AMS才會(huì)回調(diào)onServiceDisconnected()。
上面的例子玷坠,在onBind方法中蜗搔,我們返回了new MyBinder(),如果我們直接返回null的話八堡,就不會(huì)回調(diào)ServiceConnection的onServiceConnected方法了樟凄。
注意:bindservice是和組件綁定的,依賴于組件而存在兄渺,所以在頁(yè)面退出的時(shí)候一定要調(diào)用unbindService解綁缝龄,不然會(huì)拋出異常,而startService則不受啟動(dòng)服務(wù)組件的影響挂谍,可以繼續(xù)在后臺(tái)繼續(xù)執(zhí)行叔壤。
另外要注意,服務(wù)是在主線程中運(yùn)行的口叙,如果要進(jìn)行網(wǎng)絡(luò)操作炼绘,音樂播放等CPU密集型工作或者阻塞性操作,請(qǐng)開啟新線程來(lái)處理妄田。
3.startService之后調(diào)用bindService
現(xiàn)在我們看這么一種情況俺亮,我們startService之后調(diào)用bindService,看看運(yùn)行結(jié)果:
可以看到當(dāng)bindService已經(jīng)啟動(dòng)的服務(wù)時(shí)脚曾,依然可以bind成功,只是不會(huì)重新調(diào)用onCreate方法启具,接著我們調(diào)用stopService看看本讥,咦,點(diǎn)擊之后并沒有回調(diào)onDestory鲁冯,看來(lái)僅僅通過(guò)
stopService是無(wú)法停止服務(wù)了囤踩,因?yàn)樗推渌M件還綁定著呢,現(xiàn)在調(diào)下unbindService試試:
unBindService之后晓褪,服務(wù)才會(huì)銷毀堵漱,所以說(shuō)如果同時(shí)使用了start和bind啟動(dòng)服務(wù),必須要stopservice和unbindservice都執(zhí)行后服務(wù)才會(huì)銷毀涣仿。
至于先bind再start也是同樣的道理勤庐,服務(wù)的創(chuàng)建onCreate方法只會(huì)調(diào)用一次示惊,銷毀同樣需要調(diào)用stopservice和unbindservice后才能成功,具體大家可以自己實(shí)驗(yàn)感受下愉镰。
4.Service模擬下載任務(wù)
我們來(lái)模擬一個(gè)下載任務(wù)米罚,具體代碼如下:
public class DownloadService extends Service {
private ServiceHandler serviceHandler;
private NotificationManager mNotificationManager;
private final class ServiceHandler extends Handler{
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//開始處理耗時(shí)任務(wù)
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Toast.makeText(DownloadService.this, "下載任務(wù)完成···", Toast.LENGTH_SHORT).show();
mNotificationManager.cancel(100);
//任務(wù)執(zhí)行完畢,根據(jù)startId停止服務(wù)
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
HandlerThread thread = new HandlerThread("downloadTest", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
serviceHandler = new ServiceHandler(thread.getLooper());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "開始任務(wù)···", Toast.LENGTH_SHORT).show();
Message msg = Message.obtain();
msg.arg1 = startId;
serviceHandler.sendMessage(msg);
showNotifycation();
return super.onStartCommand(intent, flags, startId);
}
private void showNotifycation(){
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this,"downloadfile")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("溫馨提醒")
.setContentText("當(dāng)前正在下載中···");
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(100, mBuilder.build());
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
我們?cè)趏nCreate中創(chuàng)建了HandlerThread丈探,開啟消息循環(huán)录择,然后創(chuàng)建了Handler用來(lái)處理耗時(shí)任務(wù),此時(shí)Handler取的是HandlerThread的Looper碗降,運(yùn)行在子線程中隘竭,在onStartCommand中我們發(fā)送了message并顯示了個(gè)通知,任務(wù)sleep 5秒后結(jié)束讼渊,關(guān)閉通知并停止服務(wù)动看。運(yùn)行效果如下:
5.onStartCommand的返回值
Service作為一個(gè)組件,很難保證一直存在爪幻,當(dāng)service進(jìn)程被kill掉的時(shí)候菱皆,service會(huì)如何響應(yīng)呢?onStartCommand的返回值其實(shí)就是對(duì)應(yīng)的響應(yīng)策略:
- START_STICKY:service進(jìn)程被kill掉后挨稿,會(huì)重新創(chuàng)建service仇轻,并且調(diào)用onStartCommand(Intent,int,int)方法,但I(xiàn)ntent將為null奶甘。
2.START_NOT_STICKY:如果在執(zhí)行完onStartCommand后拯田,服務(wù)被異常kill掉,系統(tǒng)不會(huì)自動(dòng)重啟該服務(wù)甩十。
3.START_REDELIVER_INTENT:如果在執(zhí)行完onStartCommand后船庇,服務(wù)被異常kill掉,系統(tǒng)會(huì)自動(dòng)重啟該服務(wù)侣监,并將Intent的值傳入鸭轮。
4.START_STICKY_COMPATIBILITY:兼容低版本,與 START_STICKY 作用相同橄霉,但不保證每次都能重啟成功窃爷。
6.啟動(dòng)activity和彈出dialog
我們可能在某個(gè)合適的時(shí)機(jī)需要在Service里彈出Dialog,來(lái)測(cè)試下姓蜂,我們啟動(dòng)服務(wù)5秒后彈出dialog按厘,代碼如下:
public class DialogService extends Service {
private ServiceHandler serviceHandler;
private NotificationManager mNotificationManager;
private final class ServiceHandler extends Handler{
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//彈出dialog
showDialog();
}
}
private void showDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle("我是Service里的彈窗")
.setIcon(R.mipmap.ic_launcher)
.setMessage("沒想到吧!钱慢!")
.setPositiveButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(DialogService.this, "點(diǎn)擊了取消", Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
}).setNegativeButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(DialogService.this, "點(diǎn)擊了確定", Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
}).create().show();
}
@Override
public void onCreate() {
serviceHandler = new ServiceHandler(Looper.getMainLooper());
}
@Override
public int onStartCommand(Intent intent, int flags, final int startId) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = Message.obtain();
msg.arg1 = startId;
serviceHandler.sendMessage(msg);
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
然后運(yùn)行后逮京,5秒后,果然········崩潰了束莫,異常如下:
好吧懒棉,既然要主題那就給你設(shè)置個(gè):
<style name="myDialogTheme" parent="Theme.AppCompat.Light.Dialog">
<!--設(shè)置按鈕顏色-->
<item name="colorAccent">@color/brand_accent</item>
<!--設(shè)置背景顏色-->
<item name="android:windowBackground">@color/white</item>
</style>
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,R.style.myDialogTheme);
再次運(yùn)行:
崩潰也很正常草描,咱們想脫離activity彈出dialog肯定不是那么簡(jiǎn)單,經(jīng)過(guò)一番調(diào)研搜索策严,要想彈出系統(tǒng)級(jí)別的dialog需要設(shè)置dialog的窗口類型為TYPE_SYSTEM_ALERT穗慕,提高dialog的窗口等級(jí),我們添加如下代碼再次嘗試:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//8.0新特性
diaclog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY - 1);
} else {
diaclog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
記得添加權(quán)限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
運(yùn)行后妻导,終于成功彈出dialog逛绵,啟動(dòng)服務(wù)后5秒鐘彈出dialog,效果如下:
當(dāng)然在Service中彈出Dialog也可以通過(guò)啟動(dòng)透明Activity的方式倔韭,在Activity上彈出Dialog术浪,這里就不具體實(shí)現(xiàn)了,有興趣的同學(xué)可以自己實(shí)現(xiàn)感受下狐肢。
至于在Service中啟動(dòng)Activity就很簡(jiǎn)單了添吗,只需要注意要設(shè)置flag為FLAG_ACTIVITY_NEW_TASK沥曹,即在新的任務(wù)棧中啟動(dòng):
Intent intent = new Intent(this, YourActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
至于為什么要添加newtask大家可以點(diǎn)這里查看份名。
7.前臺(tái)服務(wù)
服務(wù)雖然會(huì)在后臺(tái)運(yùn)行,但系統(tǒng)在內(nèi)存不足的時(shí)候妓美,也會(huì)考慮將其終止僵腺,所以為了提高Service的存活率,我們可以通過(guò)啟動(dòng)前臺(tái)服務(wù)的方式來(lái)提高它的優(yōu)先級(jí)壶栋,前臺(tái)服務(wù)其實(shí)就是在通知欄常駐一個(gè)通知辰如,一般音樂播放器都是這種方式來(lái)處理的。
注意:前臺(tái)服務(wù)startForeground是android8.0特有的贵试,也就是說(shuō)8.0前后需要進(jìn)行適配琉兜,8.0之前繼續(xù)使用原來(lái)的NotificationCompat.Builder來(lái)創(chuàng)建通知,并設(shè)置setOngoing(true)來(lái)保持通知常駐毙玻,8.0以及8.0之后直接startForeground啟動(dòng)即可豌蟋。
這里不介紹通知的使用,具體大家可以自行查看桑滩,8.0通知改動(dòng)挺大梧疲,新增了channel的概念,大家可以自行g(shù)oogle了解运准。
下面來(lái)看下它的具體用法:
public class ForegroundService extends Service {
private NotificationManager notificationManager;
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onCreate() {
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Intent intentForeSerive = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intentForeSerive, 0);
//8.0以及8.0之后
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (null == notificationManager.getNotificationChannel("fore_service")) {
NotificationChannel channel = new NotificationChannel("fore_service", "前臺(tái)服務(wù)", NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
}
Notification notification = new NotificationCompat.Builder(this, "fore_service")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
}else {
//8.0之前的版本
Notification notification = new NotificationCompat.Builder(this, "fore_service")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setAutoCancel(false)
.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.build();
notificationManager.notify(1,notification);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int onStartCommand(Intent intent, int flags, final int startId) {
int flag = intent.getIntExtra("flag",0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if(flag == 0){
//關(guān)閉前臺(tái)服務(wù)
stopForeground(true);
stopSelf();
}
}else {
if(flag == 0){
//關(guān)閉通知
notificationManager.cancel(1);
stopSelf();
}
}
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
我通過(guò)一個(gè)按鈕來(lái)啟動(dòng)服務(wù)和關(guān)閉服務(wù):
private int currentFlag = 1;
//點(diǎn)擊按鈕之后執(zhí)行如下代碼
Intent intent = new Intent(MainActivity.this, ForegroundService.class);
intent.putExtra("flag",currentFlag);
startService(intent);
if(currentFlag == 1){
currentFlag = 0;
}else {
currentFlag = 1;
}
運(yùn)行效果如下:
8.Android 5.0之后必須顯式啟動(dòng)Service
在上面的例子中幌氮,我們都是使用Intent指定Service.class的方式顯式啟動(dòng)的,那么我們?cè)?.0以上的模擬器上運(yùn)行如下代碼:
<service android:name=".MyService">
<intent-filter>
<action android:name="com.wangkeke.service.test"/>
<category android:name="android.intent.category.default" />
</intent-filter>
</service>
Intent intent = new Intent();
intent.setAction("com.wangkeke.service.test");
startService(intent);
話不多說(shuō)胁澳,直接運(yùn)行并點(diǎn)擊啟動(dòng)Service该互,正如我們"期待的那樣",崩潰出現(xiàn)了>禄慢洋!
注意注意:Android 5.0之后必須顯式啟動(dòng)ServiceL瘤ā!普筹!