2.1 Service的基本用法
定義Service
Service是Android系統(tǒng)中的四大組件之一隅肥,它是一種長生命周期的竿奏,沒有可視化界面,運(yùn)行于后臺(tái)的一種服務(wù)程序武福。
onBind()是Service類中唯一的抽象方法议双,必須在子類里實(shí)現(xiàn)。我們會(huì)在后面的小節(jié)中使用到onBind()方法捉片。
這里我們重寫了OnCreate()平痰、OnStartCommand()和OnDestroy()這3個(gè)方法,它們是每個(gè)Service最常用的3個(gè)方法了伍纫。
public class MyService extends Service {
public MyService() { }
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d("onDestroy", "onStartCommand executed");
super.onDestroy();
}
}
在AndroidManifest.xml中注冊
每一個(gè)Service都需要在AndroidManifest.xml中文件中進(jìn)行注冊才能生效宗雇。這是Android四大組件共有的特點(diǎn)。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
啟動(dòng)和停止Service
創(chuàng)建MainActivity莹规,并修改OnCreate的代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startServiceBtn = findViewById(R.id.button1);
Button stopServiceBtn = findViewById(R.id.button2);
startServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MyService.class);
startService(intent);
}
});
stopServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MyService.class);
stopService(intent);
}
});
}
}
startService()和stopService()方法都是定義在Context類中的赔蒲,所以我們在Activity里可以直接調(diào)用這個(gè)方法。
另外良漱,Service也可以自我停止運(yùn)行舞虱,只需要我們在Service內(nèi)部調(diào)用stopSelf()方法即可。
從Android 8.0系統(tǒng)開始母市,應(yīng)用的后臺(tái)功能被大幅削減》担現(xiàn)在只有當(dāng)應(yīng)用保持在前臺(tái)可見狀態(tài)的情況下,Service才能保證穩(wěn)定運(yùn)行患久,一旦應(yīng)用進(jìn)入后臺(tái)之后椅寺,Service隨時(shí)都有可能被系統(tǒng)回收。之所以做這樣的改動(dòng)蒋失,是為了防止許多惡意的應(yīng)用程序長期在后臺(tái)占用手機(jī)資源返帕,從而導(dǎo)致手機(jī)變得越來越卡。當(dāng)然篙挽,如果需要長期在后臺(tái)執(zhí)行一些任務(wù)荆萤,可以使用前臺(tái)Service或者WorkManager。
Activity和Service進(jìn)行通信
我們希望在MyService里提供一個(gè)下載功能嫉髓,然后在Activity中可以決定何時(shí)開始下載观腊,以及隨時(shí)查看下載進(jìn)度。
實(shí)現(xiàn)這個(gè)功能的思路是創(chuàng)建一個(gè)專門的Binder對象來對下載功能進(jìn)行管理算行。修改MyService中的代碼:
public class MyService extends Service {
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload() {
Log.d("MyService", "startDownload executed");
}
public int getProgress() {
Log.d("MyService", "getProgress executed");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
......
}
當(dāng)Activity和Service綁定了之后梧油,就可以調(diào)用該Service里的Binder提供的方法了敦捧。修改MainActivity中的代碼:
public class MainActivity extends AppCompatActivity {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button bindServiceBtn = findViewById(R.id.button1);
Button stopServiceBtn = findViewById(R.id.button2);
bindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
});
stopServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
unbindService(connection);
}
});
}
}
onServiceConnected()方法方法會(huì)在Activity與Service成功綁定的時(shí)候調(diào)用想括,而onServiceDisconnected()方法只有在Service的創(chuàng)建進(jìn)程崩潰或者被殺掉的時(shí)候才會(huì)調(diào)用,這個(gè)方法不太常用辙售。
Activity和Service綁定:這里我們?nèi)匀粯?gòu)建了一個(gè)Intent對象,然后調(diào)用bindService()方法將MainActivity和MyService進(jìn)行綁定骗村。bindService()方法接收3個(gè)參數(shù)嫌褪,第一個(gè)參數(shù)就是剛剛構(gòu)建出的Intent對象,第二個(gè)參數(shù)是前面創(chuàng)建出的ServiceConnection的實(shí)例胚股,第三個(gè)參數(shù)則是一個(gè)標(biāo)志位笼痛,這里傳入BIND_AUTO_CREATE表示在Activity和Service進(jìn)行綁定后自動(dòng)創(chuàng)建Service。這會(huì)使得MyService中的onCreate()方法得到執(zhí)行琅拌,但 onStartCommand()方法不會(huì)執(zhí)行缨伊。
Activity和Service解綁:調(diào)用一下unbindService()方法就可以了。
任何一個(gè)Service在整個(gè)應(yīng)用程序范圍內(nèi)都是通用的进宝,即MyService不僅可以和MainActivity綁定刻坊,還可以和任何一個(gè)其他的Activity進(jìn)行綁定,而且在綁定完成后党晋,它們都可以獲取相同的DownloadBinder實(shí)例谭胚。
2.2 Service的生命周期
一旦在項(xiàng)目的任何位置調(diào)用了Context的startService()方法,相應(yīng)的Service就會(huì)啟動(dòng)未玻,并回調(diào)onStartCommand()方法灾而。如果這個(gè)Service之前還沒有創(chuàng)建過,onCreate()方法會(huì)先于onStartCommand()方法執(zhí)行扳剿。Service啟動(dòng)了之后會(huì)一直保持運(yùn)行狀態(tài)绰疤,直到stopService()或stopSelf()方法被調(diào)用,或者被系統(tǒng)回收舞终。注意,雖然每調(diào)用一次startService()方法癣猾,onStartCommand()就會(huì)執(zhí)行一次敛劝,但實(shí)際上每個(gè)Service只會(huì)存在一個(gè)實(shí)例。所以不管你調(diào)用了多少次startService()方法纷宇,只需調(diào)用一次stopService()或stopSelf()方法夸盟,Service就會(huì)停止。
另外像捶,還可以調(diào)用Context的bindService()來獲取一個(gè)Service的持久連接上陕,這時(shí)就會(huì)回調(diào)Service中的onBind()方法。類似地拓春,如果這個(gè)Service之前還沒有創(chuàng)建過释簿,onCreate()方法會(huì)先于onBind()方法執(zhí)行。之后硼莽,調(diào)用方可以獲取到onBind()方法里返回的IBinder對象的實(shí)例庶溶,這樣就能自由地和Service進(jìn)行通信了。只要調(diào)用方和Service之間的連接沒有斷開,Service就會(huì)一直保持運(yùn)行狀態(tài)偏螺,直到被系統(tǒng)回收行疏。
當(dāng)調(diào)用了startService()方法后,再去調(diào)用stopService()方法套像。這時(shí)Service中的onDestroy()方法就會(huì)執(zhí)行酿联,表示Service已經(jīng)銷毀了。類似地夺巩,當(dāng)調(diào)用了bindService()方法后贞让,再去調(diào)用unbindService()方法,onDestroy()方法也會(huì)執(zhí)行劲够,這兩種情況都很好理解震桶。但是需要注意,我們是完全有可能對一個(gè)Service既調(diào)用了startService()方法征绎,又調(diào)用了bindService()方法的蹲姐,根據(jù)Android系統(tǒng)的機(jī)制,一個(gè)Service只要被啟動(dòng)或者被綁定了之后人柿,就會(huì)處于運(yùn)行狀態(tài)柴墩,必須要讓以上兩種條件同時(shí)不滿足,Service才能被銷毀凫岖。所以江咳,這種情況下要同時(shí)調(diào)用stopService()和unbindService()方法,onDestroy()方法才會(huì)執(zhí)行哥放。
2.3 使用前臺(tái)Service
前臺(tái)Service概念
從Android 8.0系統(tǒng)開始歼指,只有當(dāng)應(yīng)用保持在前臺(tái)可見狀態(tài)的情況下,Service才能保證穩(wěn)定運(yùn)行甥雕,一旦應(yīng)用進(jìn)入后臺(tái)之后踩身,Service隨時(shí)都有可能被系統(tǒng)回收。
而如果你希望Service能夠一直保持運(yùn)行狀態(tài)社露,就可以考慮使用前臺(tái)Service挟阻。前臺(tái)Service和普通Service最大的區(qū)別就在于,它一直會(huì)有一個(gè)正在運(yùn)行的圖標(biāo)在系統(tǒng)的狀態(tài)欄顯示峭弟,下拉狀態(tài)欄后可以看到更加詳細(xì)的信息附鸽,非常類似于通知的效果。
由于狀態(tài)欄中一直有一個(gè)正在運(yùn)行的圖標(biāo)瞒瘸,相當(dāng)于我們的應(yīng)用以另外一種形式保持在前臺(tái)可見狀態(tài)坷备,所以系統(tǒng)不會(huì)傾向于回收前臺(tái)Service。
定義前臺(tái)Service
PendingIntent和Intent有些類似情臭,都可以去指明某一個(gè)“意圖”击你,都可以用于啟動(dòng)活動(dòng)玉组、啟動(dòng)服務(wù)以及發(fā)送廣播等。不同的是丁侄,Intent更加傾向于去立即執(zhí)行某個(gè)動(dòng)作惯雳,而PendingIntent更加傾向于在某個(gè)合適的時(shí)機(jī)去執(zhí)行某個(gè)動(dòng)作。所以鸿摇,也可以把PendingIntent簡單地理解為延遲執(zhí)行的Intent石景。
PendingIntent的用法同樣很簡單,它主要提供了幾個(gè)靜態(tài)方法用于獲取PendingIntent的實(shí)例拙吉,可以根據(jù)需求來選擇是使用getActivity()方法潮孽、getBroadcast()方法、還是getService() 方法筷黔。這幾個(gè)方法所接收的參數(shù)都是相同的往史,第一個(gè)參數(shù)依舊是Context,不用多做解釋佛舱。 第二個(gè)參數(shù)一般用不到椎例,通常都是傳入0即可。第三個(gè)參數(shù)是一個(gè)Intent對象请祖,我們可以通過這個(gè)對象構(gòu)建出PendingIntent的 “意圖”订歪。第四個(gè)參數(shù)用于確定PendingIntent的行為,F(xiàn)LAG_ONE_SHOT肆捕、FLAG_NO_CREATE刷晋、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT這四種值可選,每種值的含義你可以查看文檔慎陵,通常情況下這個(gè)參數(shù)傳入0就可以了眼虱。
public class MyService extends Service {
@Override
public void onCreate() {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
String channelId = "my_service";
String channelName = "前臺(tái)Service通知";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
}
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon))
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
}
......
}
可以看到,這里先是使用Intent表達(dá)出我們想要啟動(dòng)MainActivity的“意圖”席纽,然后將構(gòu)建好的Intent對象傳入PendingIntent的getActivity()方法里蒙幻,以得到PendingIntent的實(shí)力,接著在NotificationCompat.Builder中調(diào)用setContentIntent()方法胆筒,把它作為參數(shù)傳入即可。這樣子點(diǎn)擊一下該通知時(shí)诈豌,就會(huì)打開MainActivity的界面了仆救。
這部分代碼和創(chuàng)建通知Notification的方法類似,只不過這次在構(gòu)建Notification對象后并沒有使用NotificationManager將通知顯示出來矫渔,而是調(diào)用了StartForeground()方法彤蔽。
startForeground()方法接收兩個(gè)參數(shù):第一個(gè)參數(shù)是通知的id;第二個(gè)參數(shù)則是構(gòu)建的Notification對象庙洼。調(diào)用startForeground()方法后就會(huì)讓MyService變成一個(gè)前臺(tái)Service顿痪,并在系統(tǒng)狀態(tài)欄顯示出來镊辕。
在AndroidManifest.xml中聲明權(quán)限
另外,從Android 9.0系統(tǒng)開始蚁袭,使用前臺(tái)Service必須在AndroidManifest.xml中進(jìn)行權(quán)限聲明:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication" >
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
......
</manifest>
現(xiàn)在點(diǎn)擊“Start Service”或者“Bind Service”按鈕征懈,MyService就會(huì)以前臺(tái)Service的模式啟動(dòng)了,并且在系統(tǒng)狀態(tài)欄會(huì)顯示一個(gè)通知圖標(biāo)揩悄,下拉狀態(tài)欄可以看到通知的詳細(xì)內(nèi)容:
2.3 使用IntentService
Service處理耗時(shí)邏輯
Service中的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中的卖哎,如果直接在Service里處理一些耗時(shí)的邏輯,就很容易出現(xiàn)ANR(Application Not Responding)的情況删性。
所以這個(gè)時(shí)候就需要用到Android多線程編程的技術(shù)了亏娜,我們應(yīng)該在每個(gè)Service的每個(gè)具體方法里開啟一個(gè)子線程,然后在這里處理那些耗時(shí)的邏輯:
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 處理具體的邏輯
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
......
}
但是蹬挺,這種Service一旦啟動(dòng)维贺,就會(huì)一直處于運(yùn)行狀態(tài),必須調(diào)用stopService()或stopSelf()方法巴帮,或者被系統(tǒng)回收溯泣,Service才會(huì)停止:
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 處理具體的邏輯
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
......
}
定義IntentService
總會(huì)有一些程序員忘記開啟線程,或者忘記調(diào)用stopSelf()方法晰韵。為了可以簡單地創(chuàng)建一個(gè)異步的发乔、會(huì)自動(dòng)停止的Service,Android專門提供了一個(gè)IntentService類:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 打印當(dāng)前線程的id
Log.d("MyIntentService", "Thread id is" + Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy");
}
}
不要忘記雪猪,Service都是需要再AndroidManifest.xml里注冊的栏尚。
onHandleIntent()方法可以處理一些耗時(shí)的邏輯,而不用擔(dān)心ANR的問題只恨,因?yàn)檫@個(gè)方法已經(jīng)是在子線程中運(yùn)行的了译仗。另外,這個(gè)Service在onHandleIntent()運(yùn)行結(jié)束后會(huì)自動(dòng)停止官觅。