史上最詳細創建 Android AIDL 遠程服務步驟

項目介紹

  • 創建時間:2019年10月4日16:30:09
  • 實現功能:Android 遠程服務的製作與測試運行,AIDL服務。
  • 開發環境介紹:Android API = 29Android 10,開發IDE是 Android Studio

吐槽

網上搜了N多文章,要麼年代久遠,要麼開發IDE不同操作不懂(小白搞不懂。。),本文以最詳細的步驟實現最簡的 AIDL 遠程服務的製作和調用。

實現步驟

  • 概述

    Android Studio 中創建了空的工程(其實後來沒用到,不過爲了配合源碼還是要說下),創建模塊 rsserver - 由於 Android StudioIDEA 一個德行的,這裏只能叫它模塊了 - 英文名是 module 嗎。本模塊用來製作一個服務可以在本模塊的 activity 中調用,可以提供給其他 app 或者模塊使用,再創建一個客戶端模塊 rsclient - 這裏的 rs 表示 remote service 。那麼最終形成的結構如下圖:

    後面的步驟爲先製作服務端模塊中的服務並且測試通過後再製作客戶端模塊,讓我們開始吧。

  • 製作 AIDL 接口文件,在服務端模塊的根節點上通過右鍵菜單創建一個AIDL 文件

    將其命名爲 IProcessInfo,其中只要定義一個方法不用實現,全部代碼如下(創建完畢後會有一個默認的方法,將其刪除掉,然後追加一個我們自己的方法)

    // IProcessInfo.aidl
    package com.ccsoft.rsserver;
    
    // Declare any non-default types here with import statements
    
    interface IProcessInfo {
        int getProcessId();
    }
    

    保存後 AS 會自動創建同名的接口文件,之後創建服務的時候要用到

  • 實現 AIDL 文件中定義的方法
    在項目包 com.ccsoft.rsserver 下創建包 service 再在其下創建服務 IProcessInfoImpl 繼承自 IProcessInfo.Stub,其全部代碼如下

    package com.ccsoft.rsserver.service;
    
    import android.os.RemoteException;
    
    import com.ccsoft.rsserver.IProcessInfo;
    
    public class IProcessInfoImpl extends IProcessInfo.Stub {
        @Override
        public int getProcessId() throws RemoteException {
            return android.os.Process.myPid();
        }
    }
    
    

    調用本方法會打印進程ID,最終形成的結構如下圖

  • 創建服務,用來返回實現接口的類的實例,其全部代碼如下:

    package com.ccsoft.rsserver.service;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.util.Log;
    
    import androidx.annotation.Nullable;
    
    public class MyRemoteService extends Service {
        private static final String TAG = "chanchaw";
        IProcessInfoImpl mProcessInfo = new IProcessInfoImpl();
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            Log.e(TAG, "MyRemoteService thread id = " + Thread.currentThread().getId());
            return mProcessInfo;
        }
    }
    

    最終實現的結構如下

  • 接下來爲服務端創建一個 Activity 用來測試服務是否可用,先創建佈局文件 activity_main,其全部代碼如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="綁定本地服務"
        android:onClick="bindLocalService" />
    </LinearLayout>
    

    最終形成的結構如下圖

  • 創建一個 Activity 顯示該佈局並且測試服務,命名爲 MainActivity ,其全部代碼如下:

    package com.ccsoft.rsserver;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.util.Log;
    import android.view.View;
    
    import androidx.annotation.Nullable;
    
    import com.ccsoft.rsserver.service.MyRemoteService;
    
    public class MainActivity extends Activity {
        // 打印日誌時用來過濾,快速找到調試信息
        private static final String TAG = "chanchaw";
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 關聯到 res/layout/activity_main.xml,會顯示其中定義的控件
            setContentView(R.layout.activity_main);
        }
    
        // 創建 ServiceConnection 類型的對象實例,在後面綁定服務時會用到
        ServiceConnection myServiceConnection = new ServiceConnection() {
    
            /**
             * 服務綁定成功後會調用本方法
             * @param name
             * @param service
             */
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
                Log.i(TAG, "MyRemoteService onServiceConnected");
                // 通過aidl取出數據
                IProcessInfo processInfo = IProcessInfo.Stub.asInterface(service);
                try {
                    Log.i(TAG, "MyRemoteService process id = " + processInfo.getProcessId());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.i(TAG, "MyRemoteService onServiceDisconnected");
            }
        };
    
        public void bindLocalService(View v){
            Intent intent = new Intent(this, MyRemoteService.class);
            bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE);
    
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(myServiceConnection);
        }
    }
    

    最終形成的結構如下

  • 在模塊清單文件 AndroidManifest.xml 中註冊Activity 和服務,其全部代碼如下:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.ccsoft.rsserver">
    
        <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" >
    
            <activity android:name=".MainActivity" android:label="MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
            <service android:name=".service.MyRemoteService" android:process=":remote">
                <intent-filter>
                    <action android:name="com.jxx.server.service.bind" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </service>
    
        </application>
    </manifest>
    
  • 在服務端測試上面製作的服務,成功的話則完成了全部工作的70%,效果如下圖

  • 創建客戶端模塊,然後將服務端模塊中的 AIDL 連帶其下的文件一起拷貝到客戶端模塊中,如下圖

  • 拷貝過去後切記要使用 ctrl + F9 構建下客戶端模塊,這樣纔會生成接口文件,如下圖

  • 客戶端模塊中創建 activity 的佈局文件,其全部代碼如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/rsclient_textview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="rsclient_textview" />
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="綁定遠程服務"
            android:onClick="bindRemoteService" />
    </LinearLayout>
    

    形成的結構如下

  • 創建 Activity,全部代碼如下

    package com.ccsoft.rsclient;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.util.Log;
    import android.view.View;
    import android.widget.TextView;
    
    import androidx.annotation.Nullable;
    
    import com.ccsoft.rsserver.IProcessInfo;
    
    public class MainActivity extends Activity {
        private static final String TAG = "chanchaw";
    
        TextView text = null;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            text = findViewById(R.id.rsclient_textview);
        }
    
        // 綁定遠程服務
        public void bindRemoteService(View v){
            Intent intent = new Intent();
            intent.setAction("com.jxx.server.service.bind");//Service的action
            intent.setPackage("com.ccsoft.rsserver");//App A的包名
            bindService(intent, mServerServiceConnection, BIND_AUTO_CREATE);
        }
    
    
        ServiceConnection mServerServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
                Log.i(TAG, "MyRemoteService onServiceConnected");
                // 通過aidl取出數據
                IProcessInfo processInfo = IProcessInfo.Stub.asInterface(service);
                try {
                    Log.i(TAG, "MyRemoteService process id = " + processInfo.getProcessId());
                    text.setText("綁定成功!");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.i(TAG, "MyRemoteService onServiceDisconnected");
            }
        };
    }
    

    最終形成的結構如下

  • 清單文件中註冊 Activity,全部代碼如下

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.ccsoft.rsclient">
    
        <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" >
    
            <activity android:name=".MainActivity" android:label="MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    </manifest>
    

    這裏不用註冊遠程的服務,因爲是遠程調用的

  • 在客戶端模塊中測試調用遠程服務

結束語

源碼地址:https://gitee.com/chanchaw/rsdemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章