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

來源:互聯網
上載者:User

標籤:private   項目   sso   傳遞   protected   好的   arc   cte   之間   

文章轉載至:http://blog.csdn.net/guolin_blog/article/details/9797169

 這是郭霖寫的.......就是寫 "第一行代碼"的那個厲害人物,大師就是大師,和大師寫的文章相比自己還差的挺多

文章寫的太好了......感覺自己也寫不出如此好的介紹Service的文章,希望多轉載,讓更多的人看到

 

在上一篇文章中,我們學習了Android Service相關的許多重要內容,包括Service的基本用法、Service和Activity進行通訊、Service的銷毀方式、Service與Thread的關係、以及如何建立前台Service。以上所提到的這些知識點,基本上涵蓋了大部分日常開發工作當中可能使用到的Service技術。不過關於Service其實還有一個更加高端的提示沒有介紹,即遠程Service的用法。使用遠程Service甚至可以實現Android跨進程通訊的功能,下面就讓我們具體地學習一下。

 

如果你還沒有看過前面一篇文章,建議先去閱讀一下    Android Service完全解析,關於服務你所需知道的一切(上) ,

                        http://www.cnblogs.com/yangfengwu/p/7802140.html

 

因為本篇文章中涉及到的代碼是在上篇文章的基礎上進行修改的。

 

在上篇文章中我們知道了,Service其實是運行在主線程裡的,如果直接在Service中處理一些耗時的邏輯,就會導致程式ANR。

 

讓我們來做個實驗驗證一下吧,修改上一篇文章中建立的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的出現。

 

那麼本篇文章的主題是介紹遠程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已經在另外一個進程當中運行了,所以只會阻塞該進程中的主線程,並不會影響到當前的應用程式。

 

為了證實一下MyService現在確實已經運行在另外一個進程當中了,我們分別在MainActivity的onCreate()方法和MyService的onCreate()方法裡加入一行日誌,列印出各自所在的進程id,如下所示:

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的基礎資料型別 (Elementary Data Type)、字串、List或Map等。那麼如果我想傳遞一個自訂的類該怎麼辦呢?這就必須要讓這個類去實現Parcelable介面,並且要給這個類也定義一個同名的AIDL檔案。這部分內容並不複雜,而且和Service關係不大,所以就不再詳細進行講解了,感興趣的朋友可以自己去查閱一下相關的資料。

 

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

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.