Android入門(11)| 四大元件之服務
服務,即Service,是Android中實現程式後臺的解決方案。所謂程式後臺就是指那些不需要和使用者進行互動但是卻要求長時間執行的任務,這些任務不需要依賴任何的介面,也就是說即使服務所在的程式頁面被關閉而且使用者打開了另外的程式,服務仍然能夠保持正常的執行。

本節目錄
Android中使用多執行緒
Service預設並不會執行在子執行緒中,也不執行在一個獨立的程序中,它同樣執行在主執行緒中(UI執行緒)。換句話說,不要在Service裡執行耗時操作(例如UI的變化),除非你手動開啟一個子執行緒,否則有可能出現主執行緒被阻塞(ANR)的情況。首先來學習如何開啟一個子執行緒。
在Android中開啟子執行緒的方法其實是和Java中相似的,一般是有三種方法:
-
方法一:使用Thread
定義一個執行緒只需要繼承自Thread並且重寫Thread中的run()方法即可:
public MyThread extends Thread{ @Override public void run(){ //實現的操作 } }
在定義完執行緒之後,只需要呼叫Thread中的start()方法即可:
new MyThread().start();//啟動執行緒
-
方法二:使用Runnable
定義一個執行緒也可以讓它實現Runnable介面,然後來實現介面中的方法即可:
public MyThread implements Runnable{ @Override public void run(){ //實現的操作 } }
如果使用的是介面,則相應的啟動方式也要發生變化:
MyThread myThread = new MyThread(); new Thread(myThread).start();
-
方法三:使用匿名類
匿名類是建立執行緒比較常用的方法,它的使用最為簡潔:
new Thread(new Runnable(){ @Override public void run(){ //執行的操作 } }).start();
非同步訊息處理機制
Android是不允許在子執行緒中執行有關UI的操作的,但是在有些時候我們必須要在子執行緒裡執行一些耗時間的任務,然後根據任務的執行結果來更新相應的UI控制元件,這時我們就可以使用Android提供的一套非同步訊息處理機制。
非同步訊息處理機制主要是由4個部分組成: Message、Handle、MessageQueue和Looper 。非同步訊息處理機制的主要流程是:首先在主執行緒中建立一個Handler物件,並重寫handleMessage()的方法,接著是當子執行緒中需要進行UI操作時,就建立一個Message物件,並通過Handler將這條資訊傳送出去,之後這條資訊就會被新增的MessageQueue隊列當中等待著被handleMessage()來處理,而Looper則會在等待的過程中一直嘗試將佇列中的資訊發回到handleMessage()方法中。整個過程如圖所示:

非同步訊息處理機制
知道了具體的流程,我們可以來實踐一下,建立一個空專案,然後修改佈局:
<?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/change" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="change the text"/> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="text 1"/> </LinearLayout>
我們這裡是設定一個按鈕和一段顯示文字,功能是當點選按鈕時我們在子執行緒中改變TextView顯示的文字。接著修改主程式碼:
package com.example.yzbkaka.androidthreadtest; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnClickListener { public static final int UPDATE_TEXT = 1; TextView textView; private Handler handler = new Handler(){ public void handleMessage(Message msg){ switch (msg.what){ case UPDATE_TEXT: textView.setText("text 2"); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button change = (Button)findViewById(R.id.change); change.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()){ case R.id.change: new Thread(new Runnable() { @Override public void run() { Message message = new Message(); message.what = UPDATE_TEXT; handler.sendMessage(message); } }).start(); } } }
我們先是定義了一個整型常量UPDATE_TEXT,然後是新建了一個Handler物件並且重寫了它內部的handleMessage()方法,在這個方法中我們來對傳入的資訊進行判斷,如果發現Message的what是等於之前定義好的UPDATE_TEXT,則就在這裡(主執行緒)中更新UI。然後就是按鈕的點選方法,在點選按鈕之後我們會建立一個子執行緒,在子執行緒裡面我們定義了一個Message物件,然後指定Message.what = UPDATE_TEXT,最後是使用Handler的sendMessage()方法將該訊息傳送到佇列中等待處理。
使用服務
1.基本服務
我們先來定義一個基本服務,建立一個空專案,然後右鍵點選com.example.test—new—Service,然後會出來一個服務的建立介面:

建立介面
這裡面的Exported表示的是是否允許其他程式使用該服務,而Enable指的是是否啟用該服務。都勾選之後點選finish,接著看到MyService中的程式碼:
package com.example.yzbkaka.servicetest; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class MyService extends Service { public MyService() { } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate(){ super.onCreate(); } @Override public int onStartCommand(Intent intent,int flags,int started){ returnsuper.onStartCommand(intent,flags,started); } @Override public void onDestroy(){ super.onDestroy(); } }
一般在建立完服務之後,系統都會幫我們把程式碼中的建構函式和Service類中的唯一的抽象方法onBind()寫好,這兩個都是我們需要自己進行重寫的。後面的三個方法是我們選擇性重寫的,不過是一般的服務裡面都會使用到這三個方法,onCreate()和onDestroy()我們都很瞭解了,這裡就不多說,而onStartCommand()則是會在每次服務啟動時被呼叫。
當我們建立好服務之後就需要在活動裡面啟動和停止它,我們先在佈局中新增2個按鈕用於點選之後啟動服務和停止服務,接著修改主程式碼;
package com.example.yzbkaka.servicetest; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button start = (Button)findViewById(R.id.start); Button stop = (Button)findViewById(R.id.stop); start.setOnClickListener(this); stop.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()){ case R.id.start: Intent intent1 = new Intent(MainActivity.this,MyService.class); startService(intent1); break; case R.id.stop: Intent intent2 = new Intent(MainActivity.this,MyService.class); stopService(intent2); break; } } }
可以看到,我們在點選的方法裡面設定好了,如果是開始,則使用startService()方法並將Intent傳入進去;如果是停止,則使用stopService()方法,同樣也是將Intent傳入進去就可以了。
接著我們在服務的程式碼裡面新增日誌記錄工具來檢視服務是否呼叫和停止成功:
package com.example.yzbkaka.servicetest; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class MyService extends Service { public MyService() { } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate(){ super.onCreate(); Log.d("this","onCreate go"); } @Override public int onStartCommand(Intent intent,int flags,int started){ Log.d("this","onStartCommand go"); returnsuper.onStartCommand(intent,flags,started); } @Override public void onDestroy(){ super.onDestroy(); Log.d("this","onDestroy go"); } }
執行程式,看到logcat,我們發現確實是呼叫成功和停止成功了:

日誌
2.前臺服務
由於基本服務的系統優先度是比較低的,所以當手機的記憶體不足時就會被系統殺死程序,但如果我們設定的服務為前臺服務,則它不僅不會被殺死,還會有一個類似於通知的效果一直顯示在手機的狀態列上提示使用者服務還在執行。
建立前臺服務的方法和建立基本服務是一樣的,只不過是需要在onCreate()的方法當中修改一些程式碼即可:
@Override public void onCreate(){ super.onCreate(); Log.d("this","onCreate go"); Intent intent = new Intent(this,MainActivity.class); PendingIntent pi = PendingIntent.getActivities(this,0, intent,0); Notification notification = new NotificationCompat.Builder(this) .setContentTitle("title") .setContentText("Content") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) .setContentIntent(pi) .build(); startForeground(1,notification); }
可以看到我們在建立前臺服務的方法和建立通知的方法很像。先是為通知設定點選,即當我們點選前臺服務的時候頁面會跳到主活動當中,接著就是為我們的通知設定一些屬性,這些屬性我們之前都見過,所以這裡就不多說了。最後這裡啟動的方法不再是用NotificationMan-ager,而是使用的startForeground()的方法,這個方法需要傳入兩個引數:第一個引數是通知的id,這裡必須要為每一個服務提供一個唯一的id;第二個引數就是將Notification物件傳入進去。
執行程式我們就可以看到狀態列上顯示的前臺服務了。
3.IntentService
由於服務中的程式碼都是預設執行主執行緒當中的,所以如果我們將一些比較耗時的操作都放在服務中並讓其在主執行緒中進行,就很有可能出現ANR的情況。所以我們一般需要在服務中的onStartCommand()方法中手動的開啟子執行緒和關閉子執行緒:
@Override public int onStartCommand(Intent intent,int flags,int started){ new Thread(new Runnable() { @Override public void run() { //執行的操作 stopSelf(); } }).start(); returnsuper.onStartCommand(intent,flags,started); }
手動開啟執行緒和關閉執行緒有時候是很麻煩的,因此我們可以使用IntentService來使用系統自動的操作子執行緒。
我們先為再佈局新增一個按鈕,然後新建一個類並讓它繼承自IntentService,然後重寫它的方法:
package com.example.yzbkaka.servicetest; import android.app.IntentService; import android.content.Intent; import android.support.annotation.Nullable; import android.util.Log; public class MyIntentSrvice extends IntentService { public MyIntentSrvice() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { Log.d("MyIntentService","the thread is"+Thread.currentThread().getId()); } @Override public void onDestroy(){ Log.d("MyIntentService","onDestroy go"); } }
這裡我們首先是重寫了一個空建構函式,需要注意的是它呼叫父類的建構函式時必須要傳入引數。接著是重寫IntentService中的抽象方法onHandleIntent(),initService主要就是在這個方法中進行操作的,這裡我們只是使用日誌打印出當前的執行緒號以此來判斷是否自動操作了執行緒。最後就是重寫當服務銷燬時的方法即可。
接著我們在主程式碼中修改按鈕的點選事件:
...... case R.id.intent_service: Intent intent4 = new Intent(MainActivity.this,MyIntentSrvice.class); startService(intent4);
最後使用IntentService需要先註冊:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.yzbkaka.servicetest"> <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"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".MyService" android:enabled="true" android:exported="true"></service> <service android:name=".MyIntentSrvice"/> </application> </manifest>
執行程式,開啟日誌,發現執行緒號確實改變了,說明IntentService是自動操作了執行緒:

執行緒改變
活動和服務進行通訊
在前面建立服務的時候,我們只是在活動中對服務進行呼叫和停止操作,兩者之間並沒有過多的“交流”。其實如果想讓兩者之間進行通訊,我們可以使用之前還沒有用到過的onBind()方法來進行相關的操作。
我們接著使用上面的專案,先在服務裡面修改程式碼:
...... private TestClass myTestBinder = new TestClass(); class TestClass extends Binder{ public void testWay1(){ Log.d("this", "testWay1 go"); } public void testWay2(){ Log.d("this", "testWay1 go"); } }//內部類 public MyService() { } @Override public IBinder onBind(Intent intent) { return myTestBinder; } ......
我們在服務裡面新增一個內部類並讓它繼承自Binder,然後在這個內部類中添加了兩個模擬方法並通過日誌的方式來為之後的驗證做好準備。接著是修改主佈局中的程式碼:
...... <Button android:id="@+id/bind" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="bind"/> <Button android:id="@+id/unbind" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="unbind"/> ......
我們來為佈局新增兩個按鈕,分別表示開始繫結活動和解除繫結活動。最後是修改主程式碼:
package com.example.yzbkaka.servicetest; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ MyService.TestClass testClass; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { testClass = (MyService.TestClass) iBinder; testClass.testWay1(); testClass.testWay2(); } @Override public void onServiceDisconnected(ComponentName componentName) { } };//匿名類 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button start = (Button)findViewById(R.id.start); Button stop = (Button)findViewById(R.id.stop); Button bind = (Button)findViewById(R.id.bind); Button unbind = (Button)findViewById(R.id.unbind); start.setOnClickListener(this); stop.setOnClickListener(this); bind.setOnClickListener(this); unbind.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()){ case R.id.start: Intent intent1 = new Intent(MainActivity.this,MyService.class); startService(intent1); break; case R.id.stop: Intent intent2 = new Intent(MainActivity.this,MyService.class); stopService(intent2); break; case R.id.bind: Intent intent3 = new Intent(MainActivity.this,MyService.class); bindService(intent3,connection,BIND_AUTO_CREATE);//繫結服務 break; case R.id.unbind: unbindService(connection);//解除繫結 break; } } }
在主程式碼中我們先是建立了 MyService.TestClass的一個例項,接著是建立了一個ServiceConnection的匿名類並且在匿名類當中重寫了它的兩個方法,onServiceConnec ted()方法是當活動和服務繫結上之後被呼叫的,在這裡我們是呼叫了TestClass中的兩個方法;onServiceDisconnected()是當活動和服務解除繫結的時候呼叫的。最後就是在按鈕中啟動繫結和解綁了,啟動繫結我們使用的方法是bindService(),這個方法需要傳入三個引數:第一個引數是Intent;第二個引數是之前建立好的Connection例項;第三個引數則是一個標誌位,這裡我們使用的是系統預設的操作。而在解綁中我們直接將Connection例項傳入到unbindService()中即可。
最後執行程式,開啟日誌會發現我們已經在活動中呼叫到服務中的方法了。
服務的生命週期
首先放出Android官方的生命週期圖:

服務的生命週期
通過圖示,我們可以總結一下:
(1)當我們在活動中呼叫startService()的方法時,相應的服務就會啟動並呼叫onCreate(),接著是呼叫onStartCommand()方法,如果這個活動之前已經建立過了則不會呼叫onCreate()方法。之後服務就會一直執行下去直到呼叫了stopService()或者是stopSelf()被呼叫,這時活動就會停止下來,而服務中的onDestroy()就會執行,服務就會被銷燬。
(2)如果我們想要將活動的服務有更多的交流,則是可以使用bindService()方法來呼叫服務,接著服務中的onCrea()方法就會啟動,然後就是呼叫onBind()方法,同樣如果服務之前已經建立,則不會呼叫onCreate()方法。接著就是服務一直執行直到使用unBindService()方法,最後是呼叫onDestroy()方法來銷燬活動。
(3)如果當使用服務時即呼叫了startSer()方法和onBind()方法,則還是會和正常的一樣呼叫方法,不過如果要銷燬活動時必須是將stopService()和unBindService()方法同事呼叫之後才能執行onDestroy()方法。