乐趣区

史上最详细创建-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
       */
      @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);
  }

}


最终形成的结构如下
![](https://user-images.githubusercontent.com/29369287/66197772-ac01e400-e6cd-11e9-84c5-815d5764feec.png)

- 在模块清单文件 ``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%,效果如下图
![](https://user-images.githubusercontent.com/29369287/66197771-ab694d80-e6cd-11e9-98f6-e614ba7ec6b6.png)
- 创建客户端模块,然后将服务端模块中的 ``AIDL`` 连带其下的文件一起拷贝到客户端模块中,如下图
![](https://user-images.githubusercontent.com/29369287/66197770-ab694d80-e6cd-11e9-94a8-a1d001c2ff1c.png)
- 拷贝过去后切记要使用 ``ctrl + F9`` 构建下客户端模块,这样才会生成接口文件,如下图
![](https://user-images.githubusercontent.com/29369287/66197768-ab694d80-e6cd-11e9-9880-fa361007d38e.png)

- 客户端模块中创建 ``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>


形成的结构如下
![](https://user-images.githubusercontent.com/29369287/66197766-aad0b700-e6cd-11e9-85aa-70bec1af872b.png)

- 创建 ``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");
      }
  };

}


最终形成的结构如下
![](https://user-images.githubusercontent.com/29369287/66197765-aad0b700-e6cd-11e9-938e-e22917399fab.png)

- 清单文件中注册 ``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://user-images.githubusercontent.com/29369287/66197763-aad0b700-e6cd-11e9-9880-4a4d1a5c2c61.png)

#### 结束语
退出移动版