當咱們在程序中執行一些耗時操做時,好比發起一條網絡請求,考慮到網速等緣由,服務器未必會馬上響應咱們的請求,此時咱們就須要將這些操做放在子線程中去運行,以防止主線程被阻塞。android
新建一個類繼承自Thread,而後重寫父類的run()方法編程
class MyThread extends Thread{ @Override public void run(){ //處理具體的耗時邏輯 } }
如何啓動這個線程呢,只須要new出一個MyThread的實例,而後調用它的start()方法,這樣run()方法中的代碼就會在子線程中運行了安全
new MyThread().start();
更多的時候咱們會選擇使用實現Runnable接口的方式來定義一個線程服務器
class MyThread implements Runnable{ @Override public void run(){ //處理具體的耗時邏輯 } }
啓動該線程的方法網絡
MyThread myThread = new MyThread(); new Thread(myThread) .start();
這裏Thread的構造函數接收一個Runnable參數,而咱們new出的MyThread 正是一個實現了Runable接口的對象,因此能夠將它直接傳入Thread的構造函數裏。接着調用Thread的start()方法,run()方法中的代碼就會在子線程中運行了。
固然若是不想專門定義一個類去實現Runnable接口,也可使用匿名類的方式實現多線程
new Thread(new Runnable(){ @Override public void run(){ //處理具體的耗時邏輯 } }).start();
和許多其餘的GUI庫同樣,Android的UI也是線程不安全的,也就是說想要更新程序裏的UI元素,必須在主線程中進行,不然就會出現異常。app
下面經過一個具體的例子來驗證一下。新建一個AndroidThreadTest項目,修改activity_main.xml中的代碼:異步
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/change_text" android:text="Change Text"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:id="@+id/text" android:layout_centerInParent="true" android:textSize="20sp"/> </RelativeLayout>
佈局文件中定義了兩個控件:TextView用於在屏幕的正中央顯示一個Hello World字符串,Button用於改變TextView中顯示的內容。咱們但願在點擊Button後能夠把TextView中顯示的字符串改爲Nice to meet you。修改MainActivity中的代碼:async
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private TextView text; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (TextView)findViewById(R.id.text); Button changeText = (Button) findViewById(R.id.change_text); changeText.setOnClickListener(this); } @Override public void onClick(View v){ switch (v.getId()){ case R.id.change_text: new Thread(new Runnable(){ @Override public void run(){ text.setText("Nice to meet you"); } }).start(); break; default: break; } } }
咱們在Change Text按鈕點擊事件裏開啓了一個子線程,而後在子線程中調用TextView的setText()方法將顯示的字符串改爲Nice to meet you。代碼邏輯很簡單,不過咱們是在子線程中更新UI的。運行程序發現程序崩潰了:
從日誌中咱們能夠看出是因爲在子線程中更新UI致使的。對於這種狀況,Android提供了一套異步消息處理機制,完美的解決了在子線程中進行UI操做的問題。ide
修改MainActivity中的代碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private TextView text; public static final int UPDATE_TEXT = 1; private Handler handler = new Handler(){ public void handleMessage(Message msg){ switch (msg.what){ case UPDATE_TEXT: //在這裏進行UI操做 text.setText("Nice to meet you"); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (TextView)findViewById(R.id.text); Button changeText = (Button) findViewById(R.id.change_text); changeText.setOnClickListener(this); } @Override public void onClick(View v){ switch (v.getId()){ case R.id.change_text: new Thread(new Runnable(){ @Override public void run(){ Message message = new Message(); message.what = UPDATE_TEXT; handler.sendMessage(message);//將Message對象發送出去 } }).start(); break; default: break; } } }
這裏咱們先是定義了一個整型常量UPDATE_TEXT,用於表示更新TextView這個動做。而後新增一個Handler對象,並重寫父類的handleMessage()方法,在這裏對具體的Message進行處理。若是發現Message的what字段的值等於UPDATE_TEXT,就將 TextView顯示的內容改爲Nice to meet you。下面再來看一下 Change Text按鈕的點擊事件中的代碼。能夠看到,此次咱們並無在子線程裏直接進行UI操做。而是建立了一個Message(android.os.Message)對象,並將它的what字段的值指定爲UPDATE_TEXT
而後調用Handler的sendMessage()方法將這條Message發送出去。很快,Handler就會收到這條Message,並在handleMessage()方法中對它進行處理。注意此時handleMessage()方法中的代碼就是在主線程當中運行的了,因此咱們能夠放心地在這裏進行UI操做。接下來對Message攜帶的what字段的值進行判斷,若是等於UPDATE_TEXT,就將 TextView顯示的內容改爲 Nice to meet you。如今從新運行程序,發現UI確實更新了。
下面來分析一下Android異步消息處理機制是如何工做的
Android異步消息處理主要由4個部分組成:Message、Handle、MessageQueue和Looper。
- Message:Message是在線程之間傳遞的消息,它能夠在內部攜帶少許的信息,用於在不一樣線程之間交換數據。前面咱們使用到了Message的what字段,除此以外還可使用arg1和arg2字段來攜帶一些整型數據,使用obj字段攜帶一個Object對象。
- Handler:Handler顧名思義也就是處理者的意思,它主要用於發送和處理消息的。發送消息通常是使用Handler的sendMessage()方法,而發出的消息通過一系列的展轉處理後,最終會傳遞到Handler的handleMessage()方法中。
- MessageQueue:MessageQueue是消息隊列的意思,它主要用於存放全部經過Handler發送的消息。這部分消息會一直存在於消息隊列中,等待被處理。每一個線程中只會有一個MessageQueue對象。
- Looper:Looper是每一個線程中的MessageQueue的管家,調用Looper的loop()方法後,就會進入到一個無限循環當中,而後每當發現MessageQueue中存在一條消息,就會將它取出,並傳遞到Handler的handleMessage()方法中。每一個線程中也只會有一個Looper對象。
瞭解了 Message、 Handler、 Messagequeue以及 Looper的基本概念後,咱們再來把異步消息處理的整個流程梳理一遍。
首先須要在主線程當中建立一個Handler對象,並重寫handMessage()方法。而後當子線程中須要進行UI操做時,就建立一個 Message對象,並經過Handler將這條消息發送出去。
以後這條消息會被添加到 MessageQueue的隊列中等待被處理,而Looper則會一直嘗試從MessageQueue中取出待處理消息,最後分發回Handler的handleMessage()方法中。
因爲Handler是在主線程中建立的,因此此時handleMessage()方法中的代碼也會在主線程中運行,因而咱們在這裏就能夠安心地進行UI操做了。
整個異步消息處理機制的流程示意圖如圖所示:
爲了更加方便在子線程中對UI進行更新,Android提供了一些好用的工具,好比AsyncTask。藉助AsyncTask,即便對異步消息處理機制徹底不瞭解,也能夠很簡單的從子線程切換到主線程。因爲AsyncTask是一個抽象類,因此若是咱們想使用它,就必須建立一個子類去繼承它。在繼承時咱們能夠爲AsyncTask類指定3個泛型參數:
- Params:在執行AsyncTask時須要傳入的參數,可用於在後臺任務中使用
- Progress:後臺任務執行時,若是須要在界面上顯示當前進度,則使用這裏指定的泛型做爲進度單位
- Result:當任務執行完畢後,若是須要對結果進行返回,則使用這裏指定的泛型做爲返回值類型
所以一個最簡單的自定義AsyncTask就能夠寫成以下方式:
class DownloadTask extends AsyncTask<Void , Integer , Boolean>{ ... }
這裏咱們把AsyncTask的
第一個泛型參數指定爲Void,表示在執行AsyncTask的時候不須要傳人蔘數給後臺任務。
第二個泛型參數指定爲Integer,表示使用整型數據來做爲進度顯示單位。
第三個泛型參數指定爲Boolean,則表示使用布爾型數據來反饋執行結果。
固然,目前咱們自定義的DownloadTask仍是一個空任務,並不能進行任何實際的操做,咱們還須要去重寫 AsyncTask中的幾個方法才能完成對任務的定製。
常常須要去重寫的方法有如下4個。
- onPreExecute()
這個方法會在後臺任務開始執行以前調用,用於進行一些界面上的初始化操做,好比顯示個進度條對話框等。
- doInBackground(Params ..)
這個方法中的全部代碼都會在子線程中運行,咱們應該在這裏去處理全部的耗時任務。任務一旦完成就能夠經過return語句來將任務的執行結果返回,若是AsyncTask的第三個泛型參數指定的是Void,就能夠不返回任務執行結果。注意,在這個方法中是不能夠進行UI操做的,若是須要更新UI元素,好比說反饋當前任務的執行進度,能夠調用publishProgress(Progress..)方法來完成
- onProgressUpdate(Progress ...)
當在後臺任務中調用了publishProgress(Progress...)方法後,onProgressUpdate(Progress...)方法就會很快被調用,該方法中攜帶的參數就是在後臺任務中傳遞過來的。在這個方法中能夠對UI進行操做,利用參數中的數值就能夠對界面元素進行相應的更新。
- onPostExecute(Result)
當後臺任務執行完畢並經過return語句進行返回時,這個方法就很快會被調用。返回的數據會做爲參數傳遞到此方法中,能夠利用返回的數據來進行一些UI操做,好比說提醒任務執行的結果,以及關閉掉進度條對話框等。
所以,一個比較完整的自定義AsyncTask就能夠寫成以下方式:
class DownloadTask extends AsyncTask<Void ,Integer,Boolean> { @Override protected void onPreExecute(){ progressDialog.show(); } @Override protected Boolean doInBackground(Void... params){ try{ while (true){ int downloadPercent = doDownload(); publishProgress(downloadPercent); if(downloadPercent >= 100){ break; } } }catch (Exception e){ return false; } return true; } @Override protected void onProgressUpdate(Integer... values){ //這裏更新下載進度 progressDialog.setMessage("Downloaded "+values[0]+"%"); } @Override protected void onPostExecute(Boolean result){ progressDialog.dismiss(); if(result){ Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show(); }else { Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show(); } } }
在這個DownloadTask中
咱們在doInBackground()方法裏去執行具體的下載任務。這個方法裏的代碼都是在子線程中運行的,於是不會影響到主線程的運行。注意這裏虛構了一個doDownload()方法,這個方法用於計算當前的下載進度並返回,咱們假設這個方法已經存在了。在獲得了當前的下載進度後,下面就該考慮如何把它顯示到界面上了,因爲 doInBackground()方法是在子線程中運行的,在這裏確定不能進行UI操做,因此咱們能夠調用publishProgress()方法並將當前的下載進度傳進來,這樣onProgressUpdate()方法就會很快被調用,在這裏就能夠進行UI操做了。當下載完成後,doInBackground()方法會返回一個布爾型變量,這樣onPostExecute()方法就會很快被調用,這個方法也是在主線程中運行的。而後在這裏咱們會根據下載的結果來彈出相應的Toast提示,從而完成整個DownloadTask任務。簡單來講,使用AsyncTask的訣竅就是,在doInBackground()方法中執行具體的耗時任務,在onProgressUpdate()方法中進行UI操做,在onPostExecute()方法中執行一些任務的收尾工做。
若是想要啓動這個任務,只需編寫如下代碼便可:
new DownloadTask().execute();
以上就是AsyncTask的基本用法,怎麼樣,是否是感受簡單方便了許多?咱們並不須要去考慮什麼異步消息處理機制,也不須要專門使用一個Handler來發送和接收消息,只須要調用一下publishProgress()方法,就能夠輕鬆地從子線程切換到UI線程了。
咱們首先新建一個ServiceTest項目,而後右擊com.example.servicetest新建一個服務。服務命名爲MyService,Exported屬性表示是否容許除了當前以外的其餘程序訪問這個服務Enabled屬性表示是否啓用這個服務,這裏將兩個屬性都選上。下面是自動生成的代碼:
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類的,說明這是一個服務,其中有個onBind()方法,這是Service中惟一的一個抽象方法,因此必須在子類中實現,下面咱們重寫Service中的另一些方法:
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(); } @Override public int onStartCommand(Intent intent,int flags,int startId){ return super.onStartCommand(intent,flags,startId); } @Override public void onDestroy(){ super.onDestroy(); } }
能夠看到,這裏咱們又重寫了onCreate()、onStartCommand()和onDestroy()這3個方法,它們是每一個服務中最經常使用到的3個方法了。其中:onCreate()方法會在服務建立的時候調用
,onStartCommand()方法會在每次服務啓動的時候調用,onDestroy()方法會在服務銷燬的時候調用。一般狀況下,若是咱們但願服務一旦啓動就馬上去執行某個動做,就能夠將邏輯寫在onStartCommand()方法裏。而當服務銷燬時,咱們又應該在onDestroy()方法中去回收那些再也不使用的資源。另外須要注意,每個服務都須要在AndroidManifest.xml文件中進行註冊才能生效,不知道你有沒有發現,這是Android四大組件共有的特色。不過相信你已經猜到了,智能的Android Studio早已自動幫咱們將這一步完成了。打開AndroidManifest.rml文件瞧一瞧,代碼以下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.servicetest"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" 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> </application> </manifest>
這樣的話就已經將一個服務定義好了。
接下來咱們考慮如何去啓動以及中止這個服務。修改activity_main.xml代碼:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/start_service" android:text="Start Service"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/stop_service" android:text="Stop Service"/> </LinearLayout>
修改MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button startService = (Button) findViewById(R.id.start_service); Button stopService = (Button) findViewById(R.id.stop_service); startService.setOnClickListener(this); stopService.setOnClickListener(this); } @Override public void onClick(View v){ switch (v.getId()){ case R.id.start_service: Intent startIntent = new Intent(this,MyService.class); startService(startIntent); break; case R.id.stop_service: Intent stopIntent = new Intent(this,MyService.class); stopService(stopIntent); break; default: break; } } }
能夠看到:這裏在onCreate()方法中分別獲取到了Start Service按鈕和Stop Service按鈕的實例,並給它們註冊了點擊事件。而後在Start Service按鈕的點擊事件裏,咱們構建出了一個Intent對象,並調用startService()方法來啓動 MyService這個服務。在Stop Serivce按鈕的點擊事件裏,咱們一樣構建出了一個Intent對象,並調用stopService()方法來中止 MyService這個服務。startService()和stopService()方法都是定義在 Context類中的,因此咱們在活動裏能夠直接調用這兩個方法。注意,這裏徹底是由活動來決定服務什麼時候中止的,若是沒有點擊Stop Service按鈕,服務就會一直處於運行狀態。那服務有沒有什辦法讓自已中止下來呢?固然能夠,只須要在MyService的任何一個位置調用stopSelf()方法就能讓這個服務中止下來了。
雖然服務是在活動裏啓動的,但在啓動了服務以後,活動與服務基本就沒有什麼關係了。咱們在活動裏調用startService()方法來啓動MyService這個服務,而後MyService的onCreate()和onStartCommand()方法就會獲得執行,以後服務會一直處於運行狀態,但具體運行的是什麼邏輯,活動就控制不了了。這就相似於活動通知了服務一下:「你能夠啓動了!」而後服務就去忙本身的事情了,但活動並不知道服務到底去作了什麼事情,以及完成得如何。那麼有沒有什麼辦法能讓活動和服務的關係更緊密一些呢?例如在活動中指揮服務去幹什麼,服務就去幹什麼。固然能夠,這就須要藉助咱們剛剛忽略的onBind()方法了。好比說,目前咱們但願在MyService裏提供一個下載功能,而後在活動中能夠決定什麼時候開始下載,以及隨時查看下載進度。實現這個功能的思路是建立一個專門的Binder對象來對下載功能進行管理。修改MyService中的代碼:
能夠看到,這裏咱們新建了一個DownloadBinder類,並讓它繼承自Binder,而後在它的內部提供了開始下載以及查看下載進度的方法。固然這只是兩個模擬方法,並無實現真正的功能,咱們在這兩個方法中分別打印了一行日誌。接着,在MyService中建立了DownloadBinder的實例,而後在onBind()方法裏返回了這個實例,這樣MyService中的工做就所有完成了。下面就要看一看,在活動中如何去調用服務裏的這些方法了。先須要在佈局文件裏新增兩個按鈕,修改 activity_main.xml中的代碼:
這兩個按鈕分別是用於綁定服務和取消綁定服務的,那究竟是誰須要去和服務綁定呢?固然就是活動了,當一個活動和服務綁定了以後,就能夠調用該服務裏的Binder提供的方法了。修改MainActivity:
能夠看到,這裏咱們首先建立了一個ServiceConnection的匿名類,在裏面重寫了onServiceConnected()方法和onServiceDisconnected()方法,這兩個方法分別會在活動與服務成功綁定以及解除綁定的時候調用。
在onServiceConnected()方法中,咱們又經過向下轉型獲得了Down loadBinder的實例,有了這個實例,活動和服務之間的關係就變得很是緊密了。如今咱們能夠在活動中根據具體的場景來調用DownloadBinder中的任何public()方法,即實現了指揮服務幹什麼服務就去幹什麼的功能。這裏仍然只是作了個簡單的測試,在onServiceConnected()方法中調用了DownloadBinder的startDownload()和getProgress()方法。
固然,如今活動和服務其實還沒進行綁定呢,這個功能是在Bind Service按鈕的點擊事件裏完成的。能夠看到,這裏咱們仍然是構建出了一個Intent對象,而後調用bindService()方法將MainActivity和MyService進行綁定。
bindService()方法接收3個參數:
第一個參數就是剛剛構建出的Intent對象
第二個參數是前面建立出的ServiceConnection的實例
第三個參數則是一個標誌位,這裏傳入BIND_AUTO_CREATE表示在活動和服務進行綁定後自動建立服務
這會使得MyService中的onCreate()方法獲得執行,但onStartCommand()方法不會執行。
而後若是咱們想解除活動和服務之間的綁定該怎麼辦呢?調用一下unbindService()方法就能夠了,這也是 Unbind Service按鈕的點擊事件裏實現的功能。
以前咱們學習過了活動以及碎片的生命週期。相似地,服務也有本身的生命週期,前面咱們使用到的 onCreate()、 onStartCommand()、onBind()和 onDestroy()等方法都是在服務的生命週期內可能回調的方法。
一旦在項目的任何位置調用了Context的startService()方法,相應的服務就會啓動起來,並回調onStart Command()方法。若是這個服務以前尚未建立過, onCreate()方法會先於onStartCommand()方法執行。服務啓動了以後會一直保持運行狀態,直到stopService()或stopSelf()方法被調用。注意,雖然每調用一次startService()方法,onStartCommand()就會執行一次,但實際上每一個服務都只會存在一個實例。因此無論你調用了多少次 startservice()方法,只需調用一次stopService()或stopSelf()方法,服務就會中止下來了。
另外,還能夠調用Context的bindService()來獲取一個服務的持久鏈接,這時就會回調服務中的onBind()方法。相似地,若是這個服務以前尚未建立過,onCreate()方法會先於unBind()方法執行。以後,調用方能夠獲取到onBind()方法裏返回的IBinder對象的實例,這樣就能自由地和服務進行通訊了。只要調用方和服務之間的鏈接沒有斷開,服務就會一直保持運行狀態。
當調用了startService()方法後,又去調用stopService()方法,這時服務中的onDestroy()方法就會執行,表示服務已經銷燬了。相似地。當調用了bindService()方法後,又去調用 unbindservice()方法, ondestroy()方法也會執行,這兩種狀況都很好理解。可是須要注意,咱們是徹底有可能對一個服務既調用了startService()方法,又調用了bindService()方法的,這種狀況下該如何才能讓服務銷燬掉呢?根據 Android系統的機制,一個服務只要被啓動或者綁定了以後,就會一直處於運行狀態,必需要讓以上兩種條件同時不知足,服務才能被銷燬。因此,這種狀況下要同時調用stopService()和unbindService()方法,onDestroy()方法纔會執行
這樣你就已經把服務的生命週期完整地走了一遍。