服務做爲Android四大組件之一,是一種可在後臺執行長時間運行操做而不提供界面的應用組件。服務可由其餘應用組件啓動,並且即便用戶切換到其餘應用,服務仍將在後臺繼續運行。須要注意的是服務並不會自動開啓線程,全部的代碼都是默認運行在主線程當中的,因此須要在服務的內部手動建立子線程,並在這裏執行具體的任務,不然就有可能出現主線程被阻塞住的狀況。html
<!-- more -->java
關於多線程編程其實和Java一致,不管是繼承Thread仍是實現Runnable接口均可以實現。在Android中須要掌握的就是在子線程中更新UI,UI是由主線程來控制的,因此主線程又稱爲UI線程。android
Only the original thread that created a view hierarchy can touch its views.
雖然不容許在子線程中更新UI,可是Android提供了一套異步消息處理機制,完美解決了在子線程中操做UI的問題,那就是使用Handler。先來回顧一下使用Handler更新UI的用法:程序員
public class MainActivity extends AppCompatActivity { private static final int UPDATE_UI = 1001; private TextView textView; private Handler handler = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(@NonNull Message msg) { if(msg.what == UPDATE_UI) textView.setText("Hello Thread!"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.tv_main); } public void updateUI(View view) { // new Thread(()-> textView.setText("Hello Thread!")).start(); Error! new Thread(()->{ Message message = new Message(); message.what = UPDATE_UI; handler.sendMessage(message); }).start(); } }
使用這種機制就能夠出色地解決掉在子線程中更新UI的問題,下面就來分析一下Android異步消息處理機制到底的工做原理:Android中的異步消息處理主要由4個部分組成:Message,Handler,MessageQueue和Looper。
一、Message:線程之間傳遞的消息,它能夠在內部攜帶少許的信息,用於在不一樣線程之間交換數據。
二、Handler:處理者,它主要是用於發送和處理消息的。發送消息通常是使用Handler的sendMessage()方法,而發出的消息通過一系列地展轉處理後,最終會傳遞到Handler的handleMessage()方法中。
三、MessageQueue:消息隊列,它主要用於存放全部經過Handler發送的消息。這部分消息會一直存在於消息隊列中,等待被處理。每一個線程中只會有一個MessageQueue對象。編程
四、Looper是每一個線程中的MessageQueue的管家,調用Looper的loop()方法後,就會進入到一個無限循環當中,而後每當發現 MessageQueue 中存在一條消息,就會將它取出,並傳遞到Handler的handleMessage()方法中。每一個線程中也只會有一個Looper對象。多線程
異步消息處理整個流程:首先須要在主線程當中建立一個Handler 對象,並重寫handleMessage()方法。而後當子線程中須要進行UI操做時,就建立一個Message對象,並經過Handler將這條消息發送出去。以後這條消息會被添加到MessageQueue的隊列中等待被處理,而Looper則會一直嘗試從MessageQueue 中取出待處理消息,最後分發回 Handler 的handleMessage()方法中。因爲Handler是在主線程中建立的,因此此時handleMessage()方法中的代碼也會在主線程中運行,因而咱們在這裏就能夠安心地進行UI操做了。整個異步消息處理機制的流程以下圖所示:app
不過爲了更加方便咱們在子線程中對UI進行操做,Android還提供了另一些好用的工具,好比AsyncTask。AsyncTask背後的實現原理也是基於異步消息處理機制,只是Android幫咱們作了很好的封裝而已。首先來看一下AsyncTask的基本用法,因爲AsyncTask是一個抽象類,因此若是咱們想使用它,就必需要建立一個子類去繼承它。在繼承時咱們能夠爲AsyncTask類指定3個泛型參數,這3個參數的用途以下:異步
Params:在執行AsyncTask時須要傳入的參數,可用於在後臺任務中使用。
Progress:後臺任務執行時,若是須要在界面上顯示當前的進度,則使用這裏指定的泛型做爲進度單位。
Result:當任務執行完畢後,若是須要對結果進行返回,則使用這裏指定的泛型做爲返回值類型。ide
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private final int REQUEST_EXTERNAL_STORAGE = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void startDownload(View view) { verifyStoragePermissions(this); ProgressBar progressBar = findViewById(R.id.download_pb); TextView textView = findViewById(R.id.download_tv); new MyDownloadAsyncTask(progressBar, textView).execute("http://xxx.zip"); } class MyDownloadAsyncTask extends AsyncTask<String, Integer, Boolean> { private ProgressBar progressBar; private TextView textView; public MyDownloadAsyncTask(ProgressBar progressBar, TextView textView) { this.progressBar = progressBar; this.textView = textView; } @Override protected Boolean doInBackground(String... strings) { String urlStr = strings[0]; try { URL url = new URL(urlStr); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); InputStream inputStream = conn.getInputStream(); // 獲取文件總長度 int length = conn.getContentLength(); File downloadsDir = new File("..."); File descFile = new File(downloadsDir, "xxx.zip"); int downloadSize = 0; int offset; byte[] buffer = new byte[1024]; FileOutputStream fileOutputStream = new FileOutputStream(descFile); while ((offset = inputStream.read(buffer)) != -1){ downloadSize += offset; fileOutputStream.write(buffer, 0, offset); // 拋出任務執行的進度 publishProgress((downloadSize * 100 / length)); } fileOutputStream.close(); inputStream.close(); Log.i(TAG, "download: descFile = " + descFile.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); return false; } return true; } // 在主線程中執行結果處理 @Override protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); if(aBoolean){ textView.setText("下載完成,文件位於..xx.zip"); }else{ textView.setText("下載失敗"); } } // 任務進度更新 @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); // 收到新進度,執行處理 textView.setText("已下載" + values[0] + "%"); progressBar.setProgress(values[0]); } @Override protected void onPreExecute() { super.onPreExecute(); textView.setText("未點擊下載"); } } }
一、onPreExecute():方法會在後臺任務開始執行以前調用,用於進行一些界面上的初始化操做,好比顯示一個進度條對話框等。工具
二、doInBackground():方法中的全部代碼都會在子線程中運行,咱們應該在這裏去處理全部的耗時任務。任務一旦完成就能夠經過return語句來將任務的執行結果返回,若是 AsyncTask的第三個泛型參數指定的是Void,就能夠不返回任務執行結果。注意,在這個方法中是不能夠進行UI操做的,若是須要更新UI元素,好比說反饋當前任務的執行進度,能夠調用publishProgress()方法來完成。
三、onProgressUpdate():當在後臺任務中調用了publishProgress()方法後,onProgressUpdate()方法就會很快被調用,該方法中攜帶的參數就是在後臺任務中傳遞過來的。在這個方法中能夠對UI進行操做,利用參數中的數值就能夠對界面元素進行相應的更新。
四、onPostExecute():當後臺任務執行完畢並經過return語句進行返回時,這個方法就很快會被調用。返回的數據會做爲參數傳遞到此方法中,能夠利用返回的數據來進行一些UI操做,好比說提醒任務執行的結果,以及關閉掉進度條對話框等。
服務首先做爲Android之一,天然也要在Manifest文件中聲明,這是Android四大組件共有的特色。新建一個MyService類繼承自Service,而後再清單文件中聲明便可。
MyService.java:
public class MyService extends Service { private static final String TAG = "MyService"; public MyService() { } @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind: "); // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } }
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.tim.basic_service"> <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" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
能夠看到,MyService的服務標籤中有兩個屬性,exported屬性表示是否容許除了當前程序以外的其餘程序訪問這個服務,enabled屬性表示是否啓用這個服務。而後在MainActivity.java中啓動這個服務:
// 啓動服務 startService(new Intent(this, MyService.class));
如何中止服務呢?在MainActivity.java中中止這個服務:
Intent intent = new Intent(this, MyService.class); // 啓動服務 startService(intent); // 中止服務 stopService(intent);
其實Service還能夠重寫其餘方法:
public class MyService extends Service { private static final String TAG = "MyService"; public MyService() { } // 建立 @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate: "); } // 啓動 @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand: "); return super.onStartCommand(intent, flags, startId); } // 綁定 @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind: "); // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } // 解綁 @Override public void unbindService(ServiceConnection conn) { super.unbindService(conn); Log.i(TAG, "unbindService: "); } // 銷燬 @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy: "); } }
其實onCreate()方法是在服務第一次建立的時候調用的,而 onStartCommand()方法則在每次啓動服務的時候都會調用,因爲剛纔咱們是第一次點擊Start Service按鈕,服務此時還未建立過,因此兩個方法都會執行,以後若是再連續多點擊幾回 Start Service按鈕,就只有onStartCommand()方法能夠獲得執行了:
在上面的例子中,雖然服務是在活動裏啓動的,但在啓動了服務以後,活動與服務基本就沒有什麼關係了。這就相似於活動通知了服務一下:你能夠啓動了!而後服務就去忙本身的事情了,但活動並不知道服務到底去作了什麼事情,以及完成得如何。因此這就要藉助服務綁定了。
好比在MyService裏提供一個下載功能,而後在活動中能夠決定什麼時候開始下載,以及隨時查看下載進度。實現這個功能的思路是建立一個專門的Binder對象來對下載功能進行管理,修改MyService.java:
public class MyService extends Service { private static final String TAG = "MyService"; private DownloadBinder mBinder = new DownloadBinder(); static class DownloadBinder extends Binder { public void startDownload() { // 模擬開始下載 Log.i(TAG, "startDownload executed"); } public int getProgress() { // 模擬返回下載進度 Log.i(TAG, "getProgress executed"); return 0; } } public MyService() {} // 建立 @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate: "); } // 啓動 @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand: "); return super.onStartCommand(intent, flags, startId); } // 綁定 @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind: "); return mBinder; } // 解綁 @Override public void unbindService(ServiceConnection conn) { super.unbindService(conn); Log.i(TAG, "unbindService: "); } // 銷燬 @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy: "); } }
MainActivity.java以下:
public class MainActivity extends AppCompatActivity { private MyService.DownloadBinder downloadBinder; 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); } public void aboutService(View view) { int id = view.getId(); Intent intent = new Intent(this, MyService.class); switch (id){ case R.id.start_btn: startService(intent); break; case R.id.stop_btn: stopService(intent); break; case R.id.bind_btn: // 這裏傳入BIND_AUTO_CREATE表示在活動和服務進行綁定後自動建立服務 bindService(intent, connection, BIND_AUTO_CREATE); break; case R.id.unbind_btn: unbindService(connection); break; } } }
這個ServiceConnection的匿名類裏面重寫了onServiceConnected()方法和 onServiceDisconnected()方法,這兩個方法分別會在活動與服務成功綁定以及解除綁定的時候調用。在 onServiceConnected()方法中,經過向下轉型獲得DownloadBinder的實例,有了這個實例,活動和服務之間的關係就變得很是緊密了。如今咱們能夠在活動中根據具體的場景來調用DownloadBinder中的任何public()方法,即實現了指揮服務幹什麼服務就去幹什麼的功能(雖然實現startDownload與getProgress實現很簡單)。
須要注意的是,任何一個服務在整個應用程序範圍內都是通用的,即 MyService不只能夠和MainActivity綁定,還能夠和任何一個其餘的活動進行綁定,並且在綁定完成後它們均可以獲取到相同的DownloadBinder實例。
一旦調用了startServices()方法,對應的服務就會被啓動且回調onStartCommand(),若是服務未被建立,則會調用onCreate()建立Service對象。服務被啓動後會一直保持運行狀態,直到stopService()或者stopSelf()方法被調用。無論startService()被調用了多少次,可是隻要Service對象存在,onCreate()方法就不會被執行,因此只須要調用一次stopService()或者stopSelf()方法就會中止對應的服務。
在經過bindService()來獲取一個服務的持久鏈接的時候,這時就會回調服務中的 onBind()方法。相似地,若是這個服務以前尚未建立過,oncreate()方法會先於onBind()方法執行。以後,調用方能夠獲取到onBind()方法裏返回的IBinder對象的實例,這樣就能自由地和服務進行通訊了。只要調用方和服務之間的鏈接沒有斷開,服務就會一直保持運行狀態。
那麼即調用了startService()又調用了bindService()方法的,這種狀況下該如何才能讓服務銷燬掉呢?根據Android系統的機制,一個服務只要被啓動或者被綁定了以後,就會一直處於運行狀態,必需要讓以上兩種條件同時不知足,服務才能被銷燬。因此,這種狀況下要同時調用stopService()和 unbindService()方法,onDestroy()方法纔會執行。
上面講述了服務最基本的用法,下面來看看關於服務的更高級的技巧。
服務幾乎都是在後臺運行的,服務的系統優先級仍是比較低的,當系統出現內存不足的狀況時,就有可能會回收掉正在後臺運行的服務。若是你但願服務能夠一直保持運行狀態,而不會因爲系統內存不足的緣由致使被回收,就可使用前臺服務。好比QQ電話的懸浮窗口,或者是某些天氣應用須要在狀態欄顯示天氣。
public class FrontService extends Service { String mChannelId = "1001"; public FrontService() { } @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(); Intent intent = new Intent(this, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); Notification notification = new NotificationCompat.Builder(this, mChannelId) .setContentTitle("This is content title.") .setContentText("This is content text.") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setContentIntent(pi) .build(); startForeground(1, notification); } }
服務中的代碼都是默認運行在主線程當中的,若是直接在服務裏去處理一些耗時的邏輯,就很容易出現ANR的狀況。因此須要用到多線程編程,遇到耗時操做能夠在服務的每一個具體的方法裏開啓一個子線程,而後在這裏去處理那些耗時的邏輯。就能夠寫成以下形式:
public class OtherService extends Service { public OtherService() {} @Override public int onStartCommand(Intent intent, int flags, int startId) { new Thread(()->{ // TODO 執行耗時操做 }).start(); return super.onStartCommand(intent, flags, startId); } ... }
可是,這種服務一旦啓動以後,就會一直處於運行狀態,必須調用stopService()或者stopSelf()方法才能讓服務中止下來。因此,若是想要實現讓一個服務在執行完畢後自動中止的功能,就能夠這樣寫:
public class OtherService extends Service { public OtherService() {} @Override public int onStartCommand(Intent intent, int flags, int startId) { new Thread(()->{ // TODO 執行耗時操做 stopSelf(); }).start(); return super.onStartCommand(intent, flags, startId); } ... }
雖然這種寫法並不複雜,可是總會有一些程序員忘記開啓線程,或者忘記調用stopSelf()方法。爲了能夠簡單地建立一個異步的、會自動中止的服務,Android 專門提供了一個IntentService類,這個類就很好地解決了前面所提到的兩種尷尬,下面咱們就來看一下它的用法:
MyIntentService.java
public class MyIntentService extends IntentService { private static final String TAG = "MyIntentService"; private int count = 0; public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { count++; Log.i(TAG, "onHandleIntent: count = " + count); } }
MainActivity.java:
for (int i = 0; i < 10; i++) { Intent intent = new Intent(MainActivity.this, MyIntentService.class); startService(intent); }
參考資料:《第一行代碼》
原文地址: 《後臺默默的勞動者,探究服務》