Android Service徹底解析,關於服務你所需知道的一切(下)

在上篇文章中咱們知道了,Service實際上是運行在主線程裏的,若是直接在Service中處理一些耗時的邏輯,就會致使程序ANR。android

 

讓咱們來作個實驗驗證一下吧,修改上一篇文章中建立的ServiceTest項目,在MyService的onCreate()方法中讓線程睡眠60秒,以下所示:面試

public class MyService extends Service { ...... @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate() executed"); try { Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } }
    
    ......
 
}

 

從新運行後,點擊一下Start Service按鈕或Bind Service按鈕,程序就會阻塞住並沒有法進行任何其它操做,過一段時間後就會彈出ANR的提示框,以下圖所示。框架

 

 

 

以前咱們提到過,應該在Service中開啓線程去執行耗時任務,這樣就能夠有效地避免ANR的出現。ide

 

那麼本篇文章的主題是介紹遠程Service的用法,若是將MyService轉換成一個遠程Service,還會不會有ANR的狀況呢?讓咱們來動手嘗試一下吧。佈局

 

將一個普通的Service轉換成遠程Service其實很是簡單,只須要在註冊Service的時候將它的android:process屬性指定成:remote就能夠了,代碼以下所示:學習

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.servicetest" android:versionCode="1" android:versionName="1.0" > ...... <service android:name="com.example.servicetest.MyService" android:process=":remote" >
    </service>
 
</manifest>

 

如今從新運行程序,並點擊一下Start Service按鈕,你會看到控制檯馬上打印了onCreate() executed的信息,並且主界面並無阻塞住,也不會出現ANR。大概過了一分鐘後,又會看到onStartCommand() executed打印了出來。測試

 

爲何將MyService轉換成遠程Service後就不會致使程序ANR了呢?這是因爲,使用了遠程Service後,MyService已經在另一個進程當中運行了,因此只會阻塞該進程中的主線程,並不會影響到當前的應用程序。this

 

爲了證明一下MyService如今確實已經運行在另一個進程當中了,咱們分別在MainActivity的onCreate()方法和MyService的onCreate()方法里加入一行日誌,打印出各自所在的進程id,以下所示:spa

Log.d("TAG", "process id is " + Process.myPid());

 

再次從新運行程序,而後點擊一下Start Service按鈕,打印結果以下圖所示:線程

 

 

 

能夠看到,不只僅是進程id不一樣了,就連應用程序包名也不同了,MyService中打印的那條日誌,包名後面還跟上了:remote標識。

 

那既然遠程Service這麼好用,乾脆之後咱們把全部的Service都轉換成遠程Service吧,還免得再開啓線程了。其實否則,遠程Service非但很差用,甚至能夠稱得上是較爲難用。通常狀況下若是能夠不使用遠程Service,就儘可能不要使用它。

 

下面就來看一下它的弊端吧,首先將MyService的onCreate()方法中讓線程睡眠的代碼去除掉,而後從新運行程序,並點擊一下Bind Service按鈕,你會發現程序崩潰了!爲何點擊Start Service按鈕程序就不會崩潰,而點擊Bind Service按鈕就會崩潰呢?這是因爲在Bind Service按鈕的點擊事件裏面咱們會讓MainActivity和MyService創建關聯,可是目前MyService已是一個遠程Service了,Activity和Service運行在兩個不一樣的進程當中,這時就不能再使用傳統的創建關聯的方式,程序也就崩潰了。

 

那麼如何才能讓Activity與一個遠程Service創建關聯呢?這就要使用AIDL來進行跨進程通訊了(IPC)。

 

AIDL(Android Interface Definition Language)是Android接口定義語言的意思,它能夠用於讓某個Service與多個應用程序組件之間進行跨進程通訊,從而能夠實現多個應用程序共享同一個Service的功能。

 

下面咱們就來一步步地看一下AIDL的用法究竟是怎樣的。首先須要新建一個AIDL文件,在這個文件中定義好Activity須要與Service進行通訊的方法。新建MyAIDLService.aidl文件,代碼以下所示:

package com.example.servicetest; interface MyAIDLService { int plus(int a, int b); String toUpperCase(String str); }

 

點擊保存以後,gen目錄下就會生成一個對應的Java文件,以下圖所示:

 

 

 

而後修改MyService中的代碼,在裏面實現咱們剛剛定義好的MyAIDLService接口,以下所示:

public class MyService extends Service { ...... @Override public IBinder onBind(Intent intent) { return mBinder; } MyAIDLService.Stub mBinder = new Stub() { @Override public String toUpperCase(String str) throws RemoteException { if (str != null) { return str.toUpperCase(); } return null; } @Override public int plus(int a, int b) throws RemoteException { return a + b; } }; }

 

這裏先是對MyAIDLService.Stub進行了實現,重寫裏了toUpperCase()和plus()這兩個方法。這兩個方法的做用分別是將一個字符串所有轉換成大寫格式,以及將兩個傳入的整數進行相加。而後在onBind()方法中將MyAIDLService.Stub的實現返回。這裏爲何能夠這樣寫呢?由於Stub其實就是Binder的子類,因此在onBind()方法中能夠直接返回Stub的實現。

 

接下來修改MainActivity中的代碼,以下所示:

public class MainActivity extends Activity implements OnClickListener { private Button startService; private Button stopService; private Button bindService; private Button unbindService; private MyAIDLService myAIDLService; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { myAIDLService = MyAIDLService.Stub.asInterface(service); try { int result = myAIDLService.plus(3, 5); String upperStr = myAIDLService.toUpperCase("hello world"); Log.d("TAG", "result is " + result); Log.d("TAG", "upperStr is " + upperStr); } catch (RemoteException e) { e.printStackTrace(); } } }; ...... }

 

咱們只是修改了ServiceConnection中的代碼。能夠看到,這裏首先使用了MyAIDLService.Stub.asInterface()方法將傳入的IBinder對象傳換成了MyAIDLService對象,接下來就能夠調用在MyAIDLService.aidl文件中定義的全部接口了。這裏咱們先是調用了plus()方法,並傳入了3和5做爲參數,而後又調用了toUpperCase()方法,並傳入hello world字符串做爲參數,最後將調用方法的返回結果打印出來。

 

如今從新運行程序,並點擊一下Bind Service按鈕,能夠看到打印日誌以下所示:

 

 

 

因而可知,咱們確實已經成功實現跨進程通訊了,在一個進程中訪問到了另一個進程中的方法。

 

不過你也能夠看出,目前的跨進程通訊其實並無什麼實質上的做用,由於這只是在一個Activity裏調用了同一個應用程序的Service裏的方法。而跨進程通訊的真正意義是爲了讓一個應用程序去訪問另外一個應用程序中的Service,以實現共享Service的功能。那麼下面咱們天然要學習一下,如何才能在其它的應用程序中調用到MyService裏的方法。

 

在上一篇文章中咱們已經知道,若是想要讓Activity與Service之間創建關聯,須要調用bindService()方法,並將Intent做爲參數傳遞進去,在Intent裏指定好要綁定的Service,示例代碼以下:

Intent bindIntent = new Intent(this, MyService.class); bindService(bindIntent, connection, BIND_AUTO_CREATE);

 

這裏在構建Intent的時候是使用MyService.class來指定要綁定哪個Service的,可是在另外一個應用程序中去綁定Service的時候並無MyService這個類,這時就必須使用到隱式Intent了。如今修改AndroidManifest.xml中的代碼,給MyService加上一個action,以下所示:

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.servicetest" android:versionCode="1" android:versionName="1.0" > ...... <service android:name="com.example.servicetest.MyService" android:process=":remote" >
        <intent-filter>
            <action android:name="com.example.servicetest.MyAIDLService"/>
        </intent-filter>
    </service>
 
</manifest>

 

這就說明,MyService能夠響應帶有com.example.servicetest.MyAIDLService這個action的Intent。

 

如今從新運行一下程序,這樣就把遠程Service端的工做所有完成了。

 

而後建立一個新的Android項目,起名爲ClientTest,咱們就嘗試在這個程序中遠程調用MyService中的方法。

 

ClientTest中的Activity若是想要和MyService創建關聯其實也不難,首先須要將MyAIDLService.aidl文件從ServiceTest項目中拷貝過來,注意要將原有的包路徑一塊兒拷貝過來,完成後項目的結構以下圖所示:

 

 

而後打開或新建activity_main.xml,在佈局文件中也加入一個Bind Service按鈕:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"
     >
 
   <Button android:id="@+id/bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Bind Service"
       />
 
</LinearLayout>

 

接下來打開或新建MainActivity,在其中加入和MyService創建關聯的代碼,以下所示:

public class MainActivity extends Activity { private MyAIDLService myAIDLService; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { myAIDLService = MyAIDLService.Stub.asInterface(service); try { int result = myAIDLService.plus(50, 50); String upperStr = myAIDLService.toUpperCase("comes from ClientTest"); Log.d("TAG", "result is " + result); Log.d("TAG", "upperStr is " + upperStr); } catch (RemoteException e) { e.printStackTrace(); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button bindService = (Button) findViewById(R.id.bind_service); bindService.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.servicetest.MyAIDLService"); bindService(intent, connection, BIND_AUTO_CREATE); } }); } }

 

這部分代碼你們必定會很是眼熟吧?沒錯,這和在ServiceTest的MainActivity中的代碼幾乎是徹底相同的,只是在讓Activity和Service創建關聯的時候咱們使用了隱式Intent,將Intent的action指定成了com.example.servicetest.MyAIDLService。

 

在當前Activity和MyService創建關聯以後,咱們仍然是調用了plus()和toUpperCase()這兩個方法,遠程的MyService會對傳入的參數進行處理並返回結果,而後將結果打印出來。

 

這樣的話,ClientTest中的代碼也就所有完成了,如今運行一下這個項目,而後點擊Bind Service按鈕,此時就會去和遠程的MyService創建關聯,觀察LogCat中的打印信息以下所示:

 

 

不用我說,你們都已經看出,咱們的跨進程通訊功能已經完美實現了。

 

不過還有一點須要說明的是,因爲這是在不一樣的進程之間傳遞數據,Android對這類數據的格式支持是很是有限的,基本上只能傳遞Java的基本數據類型、字符串、List或Map等。那麼若是我想傳遞一個自定義的類該怎麼辦呢?這就必需要讓這個類去實現Parcelable接口,而且要給這個類也定義一個同名的AIDL文件。這部份內容並不複雜,並且和Service關係不大,因此就再也不詳細進行講解了,感興趣的朋友能夠本身去查閱一下相關的資料。

 

好了,結合上下兩篇,這就是關於Service你所需知道的一切。

 

 

 

但願本文對你有所幫助~~若是對軟件測試、接口測試、自動化測試、面試經驗交流感興趣能夠加入咱們。642830685,免費領取最新軟件測試大廠面試資料和Python自動化、接口、框架搭建學習資料!技術大牛解惑答疑,同行一塊兒交流。

相關文章
相關標籤/搜索