定義一個(gè)服務(wù)
-
新建一個(gè)項(xiàng)目,右擊com.example.servicetest --> New --> Service -->Service,會(huì)彈出一個(gè)窗口,如下所示:
- 可以看到這個(gè)服務(wù)的名字為MyService唧瘾,Exported表示是否允許除了當(dāng)前程序之外的其他程序訪問這個(gè)服務(wù),Enabled屬性表示是否啟用這個(gè)服務(wù),建立完成后可以看到
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");
}
}
- 自定義的MyService是繼承子Service岸裙,這里面只有一個(gè)onBind()方法,這個(gè)方法是Service中唯一一個(gè)抽象的方法速缆,所以在子類中必須實(shí)現(xiàn)降允,以后會(huì)用到的
- 這個(gè)時(shí)候Service中還是空空如也,所以就得重寫Service中的另外一些方法了艺糜,如下所示:
public class MyService extends Service {
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
- 可以看到這里重寫了3個(gè)方法剧董,
- onCreate()在服務(wù)創(chuàng)建的時(shí)候調(diào)用,
- onStartCommand()會(huì)在每次服務(wù)啟動(dòng)的時(shí)候調(diào)用破停,
- onDestroy()會(huì)在服務(wù)銷毀的時(shí)候調(diào)用
- 通常情況下翅楼,如果希望服務(wù)一旦啟動(dòng)就立刻去執(zhí)行某個(gè)動(dòng)作,就可以將邏輯寫在onStartCommand()方法中真慢,當(dāng)服務(wù)銷毀的時(shí)候毅臊,就可以在onDestroy()方法中回收那些不用的資源
- 要注意的是每一個(gè)服務(wù)要在AndroidManifest.xml文件中進(jìn)行注冊(cè),但是android Studio已經(jīng)幫我們注冊(cè)好了
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.md.server">
<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/AppTheme">
...
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
- 一個(gè)服務(wù)就這樣定義好了
啟動(dòng)和停止服務(wù)
啟動(dòng)和停止一個(gè)服務(wù)還要借助Intent來實(shí)現(xiàn)黑界,在項(xiàng)目中實(shí)戰(zhàn)一下
- 修改activity_main.xml中的代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/start_Service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="start_service"/>
<Button
android:id="@+id/stop_Service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="stop_service"/>
</LinearLayout>
- 添加兩個(gè)按鈕管嬉,一個(gè)用來啟動(dòng)服務(wù),一個(gè)用來停止服務(wù)
- 修改MainActivity中的代碼
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button start_Service = (Button)findViewById(R.id.start_Service);
Button stop_Service = (Button)findViewById(R.id.stop_Service);
start_Service.setOnClickListener(this);
stop_Service.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.start_Service:
Intent startIntent = new Intent(this,MyService.class);
// 啟動(dòng)服務(wù)
startService(startIntent);
break;
case R.id.stop_Service:
Intent stopIntent = new Intent(this,MyService.class);
// 停止服務(wù)
stopService(stopIntent);
break;
default:
break;
}
}
}
首先獲取到了兩個(gè)按鈕的實(shí)例园爷,并且綁定了點(diǎn)擊事件
在點(diǎn)擊事件中宠蚂,
首先構(gòu)建了一個(gè)Intent對(duì)象,里面?zhèn)魅胱远x的服務(wù)童社,并調(diào)用這個(gè)startService()來啟動(dòng)服務(wù)求厕,和調(diào)用stopService()方法停止停止這個(gè)服務(wù)
startService()和stopService()方法都是定義在COntext類中的,所以就可以直接調(diào)用這兩個(gè)方法
注意扰楼,這里完全是由活動(dòng)來決定服務(wù)何時(shí)停止的呀癣,如果沒有點(diǎn)擊這個(gè)stop_service按鈕,服務(wù)就會(huì)一直處于運(yùn)行狀態(tài)弦赖,這個(gè)時(shí)候怎么讓服務(wù)停止呢项栏,只要在MyService的任何一個(gè)位置調(diào)用stopSelf()方法就可以讓這個(gè)服務(wù)停止了
- 怎么才能證實(shí)服務(wù)啟動(dòng)了呢,這個(gè)時(shí)候只要在MyService的方法中添加打印日志就可以了蹬竖,如下
public class MyService extends Service {
public MyService() {
}
@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() {
super.onDestroy();
Log.d("MyService","onDestroy executed");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
-
運(yùn)行程序沼沈,
-
點(diǎn)擊Start Service按鈕就可以看到打印日志
-
點(diǎn)擊設(shè)置-->開發(fā)者選項(xiàng)--> 正在運(yùn)行的服務(wù) 就可以看到了
-
然后點(diǎn)擊Stop Servire按鈕流酬,看打印日志
此時(shí)這個(gè)服務(wù)就停止了
- 這里要注意的是onCreate()和onStartCommand()方法,
onCreate()是在服務(wù)第一次創(chuàng)建的時(shí)候調(diào)用的列另,而onStartCommand()則在每次啟動(dòng)服務(wù)的時(shí)候就會(huì)調(diào)用的芽腾,
第一次執(zhí)行兩個(gè)方法都會(huì)執(zhí)行,之后再點(diǎn)擊start Service按鈕页衙,只有onStartCommand()方法會(huì)得到執(zhí)行
服務(wù)和活動(dòng)進(jìn)行通信
雖然服務(wù)是在活動(dòng)里的摊滔,但是啟動(dòng)服務(wù)后,活動(dòng)于服務(wù)基本就沒有什么關(guān)系了店乐,因?yàn)榉?wù)里的方法得到執(zhí)行艰躺,之后服務(wù)就會(huì)一直處于運(yùn)行的狀態(tài),但是具體運(yùn)行邏輯的時(shí)候眨八,活動(dòng)控制不了服務(wù)了腺兴,有沒有辦法讓活動(dòng)這服務(wù)關(guān)聯(lián)起來?就是在活動(dòng)中指揮服務(wù),讓服務(wù)干什么踪古,服務(wù)就干什么含长,這個(gè)時(shí)候就得借助onBind()方法了
- 比如希望在MyService里提供一個(gè)下載功能,在活動(dòng)中可以決定何時(shí)開始下載伏穆,以及隨時(shí)查看下載進(jìn)度拘泞,實(shí)現(xiàn)這個(gè)功能的思路就是創(chuàng)建一個(gè)專門的Binder對(duì)象來對(duì)下載功能進(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;
}
}
- 這個(gè)時(shí)候新建一個(gè)DownloadBinder類繼承自Binder枕扫,然后在它的內(nèi)部提供了開始下載以及查看下載的方法陪腌,當(dāng)然這只是一個(gè)模擬方法,分別打印日志
- 然后在MyService中創(chuàng)建一個(gè)DownloadBinder的實(shí)例烟瞧,然后在onBind()方法中將這個(gè)實(shí)例返回
- 在活動(dòng)中如何調(diào)用服務(wù)里的這些方法诗鸭,首先在布局文件中新增兩個(gè)按鈕,修改activity.xml中的代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
...
<Button
android:id="@+id/bind_Service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="bind_Service"/>
<Button
android:id="@+id/unbind_Service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="unbind_Service"/>
</LinearLayout>
- 這兩個(gè)按鈕分別用于綁定服務(wù)和取消綁定服務(wù)参滴,
- 當(dāng)一個(gè)活動(dòng)和服務(wù)綁定了之后强岸,就可以調(diào)用該服務(wù)中的Bunder提供的方法,修改MainActivity中的代碼
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
// 創(chuàng)建一個(gè)匿名類
private ServiceConnection connection = new ServiceConnection() {
// 綁定成功調(diào)用
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//通過向下轉(zhuǎn)型得到了DownloadBinder的實(shí)例
downloadBinder = (MyService.DownloadBinder) iBinder;
// 調(diào)用方法
downloadBinder.startDownload();
downloadBinder.getProgress();
}
// 解除綁定調(diào)用
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button start_Service = (Button)findViewById(R.id.start_Service);
Button stop_Service = (Button)findViewById(R.id.stop_Service);
start_Service.setOnClickListener(this);
stop_Service.setOnClickListener(this);
Button bind_Service = (Button)findViewById(R.id.bind_Service);
Button unbind_Service = (Button)findViewById(R.id.unbind_Service);
bind_Service.setOnClickListener(this);
unbind_Service.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.start_Service:
Intent startIntent = new Intent(this,MyService.class);
// 啟動(dòng)服務(wù)
startService(startIntent);
break;
case R.id.stop_Service:
Intent stopIntent = new Intent(this,MyService.class);
// 停止服務(wù)
stopService(stopIntent);
break;
case R.id.bind_Service:
Intent bindIntent = new Intent(this,MyService.class);
// 綁定服務(wù)砾赔,第二個(gè)參數(shù)是前面創(chuàng)建ServiceConnection的實(shí)例
bindService(bindIntent,connection,BIND_AUTO_CREATE)
break;
case R.id.unbind_Service:
// 解除綁定
unbindService(connection);
break;
default:
break;
}
}
}
首先創(chuàng)建一個(gè)ServiceConnection匿名類蝌箍,這個(gè)類中重寫onServiceConnected()和onServiceDisconnected()方法,這兩個(gè)方法分別用于活動(dòng)與服務(wù)成功綁定和解除綁定的時(shí)候調(diào)用暴心,
在onServiceConnected()方法中妓盲,通過向下轉(zhuǎn)型得到了 DownloadBinder的實(shí)例,通過這個(gè)實(shí)例专普,就可以調(diào)用 DownloadBinder中的任何方法了
當(dāng)然了悯衬,這只是基礎(chǔ)工作,真正的綁定是在點(diǎn)擊按鈕的時(shí)候檀夹,首先構(gòu)建一個(gè)Intent對(duì)象
然后調(diào)用bindService()方法筋粗,第一個(gè)參數(shù)是Intent對(duì)象策橘,第二個(gè)參數(shù)是前面創(chuàng)建ServiceConnection的實(shí)例,第三個(gè)參數(shù)是一個(gè)標(biāo)志位亏狰,BIND_AUTO_CREATE表示在活動(dòng)和服務(wù)進(jìn)行綁定后自動(dòng)創(chuàng)建活動(dòng)役纹,這個(gè)時(shí)候就會(huì)使得MyService中的onCreate()方法得到執(zhí)行偶摔,但是onStartCommand()方法不會(huì)執(zhí)行
解除綁定只要調(diào)用unbindService()方法就可以暇唾,傳入的參數(shù)也是前面創(chuàng)建ServiceConnection的實(shí)例
-
運(yùn)行程序,就可以看到
點(diǎn)擊這個(gè)Bind Service按鈕,看打印日志
可以看到辰斋,onCreate()方法先得到執(zhí)行策州,然后調(diào)用的方法也得到了執(zhí)行 - 注意:任何一個(gè)服務(wù)在整個(gè)應(yīng)用程序范圍內(nèi)都是通用的,即MyService不僅可以和MainActivity綁定宫仗,還可以和任何一個(gè)其他活動(dòng)進(jìn)行綁定够挂,而且在綁定完成后他們都可以獲取到相同的DownloadBinder實(shí)例
活動(dòng)的生命周期
- 一旦在項(xiàng)目中的任何位置調(diào)用了Context的startServive()方法,相應(yīng)的服務(wù)就會(huì)得到執(zhí)行藕夫,并且回調(diào)onStartCommand()方法孽糖,如果服務(wù)第一次創(chuàng)建,那么onCreate()方法就會(huì)先于onStartCommand()方法執(zhí)行毅贮,
- 服務(wù)啟動(dòng)了就會(huì)一直保持運(yùn)行狀態(tài)办悟,直到stopService()或者是stopSelf()方法被調(diào)用,
注意雖然每一次都是調(diào)用startServive()方法滩褥,但是每個(gè)服務(wù)都只會(huì)存在一個(gè)實(shí)例病蛉,只要調(diào)用一次stopService()或者是stopSelf()方法,服務(wù)就會(huì)停止下來
- 另外瑰煎,還可以調(diào)用Context的bindService()來獲取一個(gè)服務(wù)的持久連接铺然,這時(shí)就會(huì)回調(diào)服務(wù)中的onBind()方法,這時(shí)酒甸,如果服務(wù)第一次創(chuàng)建魄健,那么onCreate()方法就會(huì)先于onBind()方法執(zhí)行,之后就可以獲取到onBind()方法里返回的IBinder對(duì)象實(shí)例插勤,這樣就能自由的和服務(wù)進(jìn)行通信沽瘦,只要調(diào)用方和服務(wù)之間的連接沒有斷開,服務(wù)就會(huì)一直保持運(yùn)行狀態(tài)
- 當(dāng)調(diào)用startService()方法之后饮六,又去調(diào)用stopService()方法其垄,這是服務(wù)中的onDestroy()方法就會(huì)得到執(zhí)行,這個(gè)時(shí)候服務(wù)就銷毀了卤橄,類似的绿满,當(dāng)調(diào)用了bindService()方法之后,調(diào)用了unbindService()方法窟扑,服務(wù)中的onDestroy()方法就會(huì)得到執(zhí)行喇颁,這個(gè)時(shí)候服務(wù)就銷毀了
要注意的是漏健,對(duì)一個(gè)服務(wù)調(diào)用了startService(),又調(diào)用了bindService()方法之后橘霎,這個(gè)時(shí)候要調(diào)用stopService()和unbindService()方法蔫浆,onDestroy()方法才會(huì)執(zhí)行
服務(wù)的更多技巧
使用前臺(tái)服務(wù)
服務(wù)一般都是在后臺(tái)運(yùn)行的,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存不足的時(shí)候姐叁,就有可能回收掉正在后臺(tái)運(yùn)行的服務(wù)瓦盛,如果想要服務(wù)一直保持運(yùn)行狀態(tài),而不會(huì)由于系統(tǒng)內(nèi)存不足的原因?qū)е卤换厥胀馇保涂梢钥紤]使用前臺(tái)服務(wù)了
- 前臺(tái)服務(wù)與普通服務(wù)最大的區(qū)別就是它會(huì)一直有一個(gè)正在運(yùn)行的圖標(biāo)在系統(tǒng)的狀態(tài)欄顯示原环,下拉可以看到更加詳細(xì)的信息,比如說天氣預(yù)報(bào)這種應(yīng)用处窥,一般都會(huì)在系統(tǒng)欄一直顯示當(dāng)前的天氣
- 創(chuàng)建一個(gè)前臺(tái)的服務(wù)嘱吗,修改MyService中的代碼
public class MyService extends Service {
...
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService","onCreate executed");
Intent intent = new Intent(this,MainActivity.class);
PendingIntent p1 = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new NotificationCompat.Builder(this)
.setContentText("this is title")
.setContentText("thsi is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(p1)
.build();
// 這里與之前不同
startForeground(1,notification);
}
...
}
- 別的還保持不變,只需要修改onCreate()方法里的代碼就可以了滔驾,這些代碼就是之前學(xué)過的創(chuàng)建通知的方法
- 只不過這里沒有使用 NotificationManager來將通知顯示出來谒麦,而是使用了 startForeground()方法,類似與notity()方法,第一個(gè)參數(shù)是通知id哆致,第二個(gè)參數(shù)是構(gòu)建的Notification對(duì)象绕德,調(diào)用這個(gè)startForeground()方法就會(huì)讓MyService變成一個(gè)前臺(tái)服務(wù),并在系統(tǒng)狀態(tài)欄顯示出來
-
重新運(yùn)行程序沽瞭,點(diǎn)擊Start Service按鈕迁匠,MyService就會(huì)以前臺(tái)的模式啟動(dòng)了,并且在系統(tǒng)狀態(tài)欄會(huì)顯示一個(gè)通知的圖標(biāo)驹溃,下拉狀態(tài)欄就可以看到該通知的詳細(xì)內(nèi)容
使用IntentService
服務(wù)中的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中的城丧,如果直接在服務(wù)中處理一些耗時(shí)的邏輯,就容易出現(xiàn)ANR的情況豌鹤,所以這個(gè)時(shí)候就使用到了Android多線程編程技術(shù)了
- 我們應(yīng)該在服務(wù)的每個(gè)具體的方法里開啟一個(gè)子線程亡哄,然后在這里去處理一些耗時(shí)的邏輯,因此一個(gè)比較標(biāo)準(zhǔn)的服務(wù)就可以寫成
@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);
}
- 但是這種服務(wù)一旦啟動(dòng)就會(huì)一直處于運(yùn)行的狀態(tài)布疙,必須調(diào)用stopService()或者stopSelf()方法才能讓服務(wù)停止下來蚊惯,所以想要實(shí)現(xiàn)讓一個(gè)服務(wù)在執(zhí)行完畢后自動(dòng)停止下來就可以這樣寫
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 處理具體的邏輯
// 處理完邏輯,自動(dòng)停止下來
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
- 但是這樣寫的話就太麻煩了灵临,這個(gè)時(shí)候ANdroid專門提供了一個(gè)IntentService類截型,這個(gè)類就可以解決前面遇到的麻煩
- 新建一個(gè)MyIntentServive類繼承自IntentServive
public class MyIntentServive extends IntentService {
public MyIntentServive() {
// 調(diào)用父類有參的構(gòu)造函數(shù),
super("MyIntentServive");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 具體的邏輯操作
// 打印當(dāng)前的線程的id
Log.d("MyIntentService","Thread is "+Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService","onDestroy executed");
}
}
- 首先提供一個(gè)無參的構(gòu)造函數(shù)儒溉,并且在內(nèi)部調(diào)用父類的有參構(gòu)造函數(shù)
- 這里要注意的就是onHandleIntent()方法宦焦,這個(gè)方法已經(jīng)是在子線程中運(yùn)行的了,為了證實(shí),打印了當(dāng)前進(jìn)程的id波闹,而且這個(gè)服務(wù)在運(yùn)行結(jié)束后就會(huì)自動(dòng)的停止酝豪,在onDestroy()中也打印一句話
- 修改activity_main.xml中的代碼,加入一個(gè)MyIntentService這個(gè)服務(wù)的按鈕
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
...
<Button
android:id="@+id/start_intent_Service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="MyIntentService"/>
</LinearLayout>
- 修改MainActivity中的代碼
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button startIntentService = (Button)findViewById(R.id.start_intent_Service);
startIntentService.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
...
case R.id.start_intent_Service:
// 打印主線程id
Log.d("MainActivity","Thread is "+ Thread.currentThread().getId());
Intent intentService = new Intent(this,MyIntentServive.class);
startService(intentService);
break;
default:
break;
}
}
}
- 別忘了精堕,還要在AndroidManifest.xml中進(jìn)行注冊(cè)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.md.server">
<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/AppTheme">
...
<service android:name=".MyIntentServive" />
</application>
</manifest>
-
這個(gè)時(shí)候運(yùn)行程序孵淘,就會(huì)看到
-
點(diǎn)擊MyIntentService按鈕,這個(gè)時(shí)候看打印的日志
- 可以看到此時(shí)線程不同歹篓,而且onDestroy()方法也得到了執(zhí)行瘫证,說明是MyIntentService在運(yùn)行完畢后自動(dòng)停止了