Android:遠程服務Service(含AIDL & IPC講解)


前言

  • Service作為Android四大組件之一,應用非常廣泛
  • 本文將介紹Service其中一種常見用法:遠程Service

    如果你對Service還未了解,建議先閱讀我寫的另外一篇文章:
    Android四大組件:Service史上最全面解析


目錄


目錄

1. 遠程服務與本地服務的區別

  • 遠程服務與本地服務最大的區別是:遠程Service與調用者不在同一個進程裡(即遠程Service是運行在另外一個進程);而本地服務則是與調用者運行在同一個進程裡
  • 二者區別的詳細區別如下圖:

按運行地點分類

2. 使用場景

多個應用程序共享同一個後臺服務(遠程服務)

即一個遠程Service與多個應用程序的組件(四大組件)進行跨進程通信


使用場景

3. 具體使用

  • 為了讓遠程Service與多個應用程序的組件(四大組件)進行跨進程通信(IPC),需要使用AIDL

    1. IPC:Inter-Process Communication,即跨進程通信
    2. AIDL:Android Interface Definition Language,即Android接口定義語言;用於讓某個Service與多個應用程序組件之間進行跨進程通信,從而可以實現多個應用程序共享同一個Service的功能。
  • 在多進程通信中,存在兩個進程角色(以最簡單的為例):服務器端和客戶端

  • 以下是兩個進程角色的具體使用步驟:
    服務器端(Service)
    步驟1:新建定義AIDL文件,並聲明該服務需要向客戶端提供的接口
    步驟2:在Service子類中實現AIDL中定義的接口方法,並定義生命週期的方法(onCreat、onBind()、blabla)
    步驟3:在AndroidMainfest.xml中註冊服務 & 聲明為遠程服務

    客戶端(Client)
    步驟1:拷貝服務端的AIDL文件到目錄下
    步驟2:使用Stub.asInterface接口獲取服務器的Binder,根據需要調用服務提供的接口方法
    步驟3:通過Intent指定服務端的服務名稱和所在包,綁定遠程Service

接下來,我將用一個具體實例來介紹遠程Service的使用


4. 具體實例

  • 實例描述:客戶端遠程調用服務器端的遠程Service
  • 具體使用:

4.1 服務器端(Service)

新建一個服務器端的工程:Service - server

先下Demo再看,效果會更好:Github_RemoteService_Server

步驟1. 新建一個AIDL文件


新建AIDL文件

步驟2. 在新建AIDL文件裡定義Service需要與Activity進行通信的內容(方法),並進行編譯(Make Project)

// 在新建的AIDL_Service1.aidl裡聲明需要與Activity進行通信的方法
package scut.carson_ho.demo_service;

interface AIDL_Service1 { void AIDL_Service(); } //AIDL中支持以下的數據類型 //1. 基本數據類型 //2. String 和CharSequence //3. List 和 Map ,List和Map 對象的元素必須是AIDL支持的數據類型; //4. AIDL自動生成的接口(需要導入-import) //5. 實現android.os.Parcelable 接口的類(需要導入-import)


編譯

步驟3:在Service子類中實現AIDL中定義的接口方法,並定義生命週期的方法(onCreat、onBind()、blabla)
MyService.java

public class MyService extends Service {
<span class="hljs-comment">// 實例化AIDL的Stub類(Binder的子類)</span>
AIDL_Service1.Stub mBinder = <span class="hljs-keyword">new</span> AIDL_Service1.Stub() {

    <span class="hljs-comment">//重寫接口裡定義的方法</span>
    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">AIDL_Service</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> RemoteException </span>{
        System.out.println(<span class="hljs-string">"客戶端通過AIDL與遠程後臺成功通信"</span>);
    }
};

//重寫與Service生命週期的相關方法 @Override public void onCreate() { super.onCreate();

    System.out.println(<span class="hljs-string">"執行了onCreat()"</span>);

}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">onStartCommand</span><span class="hljs-params">(Intent intent, <span class="hljs-keyword">int</span> flags, <span class="hljs-keyword">int</span> startId)</span> </span>{
    System.out.println(<span class="hljs-string">"執行了onStartCommand()"</span>);
    <span class="hljs-function"><span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.<span class="hljs-title">onStartCommand</span><span class="hljs-params">(intent, flags, startId)</span></span>;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">onDestroy</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">super</span>.onDestroy();
    System.out.println(<span class="hljs-string">"執行了onDestory()"</span>);
}

<span class="hljs-meta">@Nullable</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function">IBinder <span class="hljs-title">onBind</span><span class="hljs-params">(Intent intent)</span> </span>{

    System.out.println(<span class="hljs-string">"執行了onBind()"</span>);
<span class="hljs-comment">//在onBind()返回繼承自Binder的Stub類型的Binder,非常重要</span>
    <span class="hljs-keyword">return</span> mBinder;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">onUnbind</span><span class="hljs-params">(Intent intent)</span> </span>{
    System.out.println(<span class="hljs-string">"執行了onUnbind()"</span>);
    <span class="hljs-function"><span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.<span class="hljs-title">onUnbind</span><span class="hljs-params">(intent)</span></span>;
}

}

步驟4:在AndroidMainfest.xml中註冊服務 & 聲明為遠程服務

<service
            android:name=".MyService"
            android:process=":remote"  //將本地服務設置成遠程服務
            android:exported="true"      //設置可被其他進程調用
            >
            //該Service可以響應帶有scut.carson_ho.service_server.AIDL_Service1這個action的Intent。
            //此處Intent的action必須寫成“服務器端包名.aidl文件名”
            <intent-filter>
                <action android:name="scut.carson_ho.service_server.AIDL_Service1"/>
            </intent-filter>
    <span class="hljs-tag">&lt;/<span class="hljs-name">service</span>&gt;</span></span></code></pre>

至此,服務器端(遠程Service)已經完成了。

4.2 客戶端(Client)

新建一個客戶端的工程:Service - Client

先下Demo再看,效果會更好:Github_RemoteService_Client

步驟1:將服務端的AIDL文件所在的包複製到客戶端目錄下(Project/app/src/main),並進行編譯

注:記得要原封不動地複製!!什麼都不要改!


複製後的目錄

步驟2:在主佈局文件定義“綁定服務”的按鈕
MainActivity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="scut.carson_ho.service_client.MainActivity">
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
    <span class="hljs-attr">android:layout_centerInParent</span>=<span class="hljs-string">"true"</span>
    <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/bind_service"</span>
    <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
    <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
    <span class="hljs-attr">android:text</span>=<span class="hljs-string">"綁定服務"</span>
    /&gt;</span>

</RelativeLayout>

步驟3:在MainActivity.java裡

  • 使用Stub.asInterface接口獲取服務器的Binder;
  • 通過Intent指定服務端的服務名稱和所在包,進行Service綁定;
  • 根據需要調用服務提供的接口方法。

MainActivity.java

public class MainActivity extends AppCompatActivity {
    <span class="hljs-keyword">private</span> Button bindService;

    <span class="hljs-comment">//定義aidl接口變量</span>
    <span class="hljs-keyword">private</span> AIDL_Service1 mAIDL_Service;

    <span class="hljs-comment">//創建ServiceConnection的匿名類</span>
    <span class="hljs-keyword">private</span> ServiceConnection connection = <span class="hljs-keyword">new</span> ServiceConnection() {

        <span class="hljs-comment">//重寫onServiceConnected()方法和onServiceDisconnected()方法</span>
        <span class="hljs-comment">//在Activity與Service建立關聯和解除關聯的時候調用</span>
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onServiceDisconnected</span><span class="hljs-params">(ComponentName name)</span> </span>{
        }

        <span class="hljs-comment">//在Activity與Service建立關聯時調用</span>
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onServiceConnected</span><span class="hljs-params">(ComponentName name, IBinder service)</span> </span>{

            <span class="hljs-comment">//使用AIDLService1.Stub.asInterface()方法獲取服務器端返回的IBinder對象</span>
            <span class="hljs-comment">//將IBinder對象傳換成了mAIDL_Service接口對象</span>
            mAIDL_Service = AIDL_Service1.Stub.asInterface(service);

            <span class="hljs-keyword">try</span> {

                <span class="hljs-comment">//通過該對象調用在MyAIDLService.aidl文件中定義的接口方法,從而實現跨進程通信</span>
                mAIDL_Service.AIDL_Service();

            } <span class="hljs-keyword">catch</span> (RemoteException e) {
                e.printStackTrace();
            }
        }
    };


    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(Bundle savedInstanceState)</span> </span>{
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bindService = (Button) findViewById(R.id.bind_service);

        <span class="hljs-comment">//設置綁定服務的按鈕</span>
        bindService.setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() {
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span><span class="hljs-params">(View v)</span> </span>{

                <span class="hljs-comment">//通過Intent指定服務端的服務名稱和所在包,與遠程Service進行綁定</span>
                <span class="hljs-comment">//參數與服務器端的action要一致,即"服務器包名.aidl接口文件名"</span>
                Intent intent = <span class="hljs-keyword">new</span> Intent(<span class="hljs-string">"scut.carson_ho.service_server.AIDL_Service1"</span>);

                <span class="hljs-comment">//Android5.0後無法只通過隱式Intent綁定遠程Service</span>
                <span class="hljs-comment">//需要通過setPackage()方法指定包名</span>
                intent.setPackage(<span class="hljs-string">"scut.carson_ho.service_server"</span>);

                <span class="hljs-comment">//綁定服務,傳入intent和ServiceConnection對象</span>
                bindService(intent, connection, Context.BIND_AUTO_CREATE);

            }
        });
    }

}</code></pre>

4.3 測試結果


點擊綁定服務按鈕

從上面測試結果可以看出:

  • 打印的語句分別運行在不同進程(看語句前面的包名);
  • 客戶端調用了服務端Service的方法

客戶端和服務端進行了跨進程通信

4.4 Demo地址


5. 總結


請點贊!因為你們的贊同/鼓勵是我寫作的最大動力!

相關文章閱讀
Android開發:最全面、最易懂的Android屏幕適配解決方案
Android開發:Handler異步通信機制全面解析(包含Looper、Message Queue)
Android開發:頂部Tab導航欄實現(TabLayout+ViewPager+Fragment)
Android開發:底部Tab菜單欄實現(FragmentTabHost+ViewPager)
Android開發:JSON簡介及最全面解析方法!
Android開發:XML簡介及DOM、SAX、PULL解析對比



书籍推荐