Android AIDL实现开放系统级API 提供三方app调用

需求场景

当上层app需要调用一些系统底层的资源以及属性操作(比如Settings.system 属性的读写),甚至驱动节点的读写操作,上层app毫无疑问是没有权限的,所以就需要我们在framework 系统层做一个中转和代理,也就是做一个远程服务(AIDL实现,开放远程调用接口)。这种实现一般有两种思路

framework里实现一个系统级服务,开机自启,注册到系统服务的启动流程里,这种思路,针对像对写敏感,加了系统级进程用户组的检查的话,比如Settings.system的写操作,还是会被拦下,故不再赘述。

实现一个系统级的app,包装一个服务,封装好逻辑后,放到系统源码里参与编译即可。实测可行,并且由于是做app,在AS中直接开发,有语法提示高亮错误提醒,开发相当友好!

所以下面就讲解实现系统级APP包装远程服务的具体实现的步骤,主要就是围绕AIDL的实现

> 服务端

AIDL服务声明

AS 直接在app 主module 上右键,new -> AIDL -> AIDL File

例如 新建 ITestService.aidl

AS 会自动完成AIDL的中间类的创建,项目下新建aild 目录,与java同级,包名路径也保持一致

复制代码
package com.xxx.server;

interface ITestService {

	int getScreenBrightness();
	boolean setScreenBrightness(int brightness);
}

这里我们实现一个 控制屏幕亮度的设置,针对系统Settings属性的读写操作

Java服务实现

继承Service ,实现onBind(Intent intent) ,返回IBinder对象。

实现ITestService.Stub(),即ITestService AIDL接口的具体实现

重写实现ITestService接口声明的方法

复制代码
package com.xxx.server;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
import androidx.annotation.Nullable;

public class xxxManagerService extends Service{

	@Nullable
	@Override
	public IBinder onBind(Intent intent) {
    	return testStub;
	}
	
	private final ITestService.Stub testStub = new ITestService.Stub() {
		
		@Override
    	public int getScreenBrightness() throws RemoteException {
        	int result = -1;
        	try {
            	result = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);
        	} catch (Settings.SettingNotFoundException e) {
            	e.printStackTrace();
        	}
        	return result;
    	}

    	@Override
    	public boolean setScreenBrightness(int brightness) throws RemoteException {

        	boolean optResult;
        	int resultBrightness = 102;// default brightness 102 40%
        	if (brightness <= 255 && brightness > 10) {
            	resultBrightness = brightness;
        	}
        	// 自动亮度改为手动亮度
        	try {
            	int mode = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE);
            	if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
                	Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE,
                        Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
            	}
        	} catch (Settings.SettingNotFoundException e) {
            	e.printStackTrace();
        	}
        		optResult = Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, resultBrightness);
        		return optResult;
    	}
	}

}

AndroidManifest.xml的配置

对于只是包装一个服务,我们不再需要UI界面,不在需要它像普通app那样安装显示在桌面上

manifest根节点添加android:sharedUserId="android.uid.system" 属性,即让该app加入系统进程组,即变成系统级权限app,拥有对系统级限制的操作有了权限

复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.xxx.server"
	android:sharedUserId="android.uid.system"
	xmlns:tools="http://schemas.android.com/tools">
  • 去掉activity的注册,同时项目内的activity的实现和layout都可以删除掉了。然后注册我们的主角xxxManagerService

    注意: enabled 与 exported 必须true ; action为自定义

    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" tools:targetApi="31">

    复制代码
      <service android:name=".xxxManagerService"
          android:enabled="true"
          android:exported="true">
          <intent-filter>
              <action android:name="action.com.xxx.server"/>
              <category android:name="android.intent.category.DEFAULT"/>
          </intent-filter>
      </service>
    </application>

到这里 ,作为服务端,开放给上层的调用的配置基本就算完整了。但是实际需求,可能还会涉及到 AIDL 传输非基本数据类型的情况,AIDL 里做个对象的声明实现即可,具体不再展开

还有可能会涉及到,服务端主动回调客户端的情况

服务端回调客户端

同包名下新建ITestCallback.aidl,声明回调接口的方法

复制代码
package com.xxx.server;

interface ITestCallback {

	void onRemoteCallback(String msg);
	
}

然后在ITestService下面加入注册和解注册的方法
注意: 这里要把回调接口导包进来,尽管他们的包名路径一样

复制代码
import com.xxx.server.ITestCallback;
interface ITestService {

	int getScreenBrightness();
	boolean setScreenBrightness(int brightness);
	
	// 加入回调接口的注册和解注册
	void registerCallBack(ITestCallback callback);
	void unregisterCallBack(ITestCallback callback);

}

Java 服务接口实现里,加入刚才两个方法的实现

复制代码
package com.xxx.server;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
import androidx.annotation.Nullable;

public class xxxManagerService extends Service{

	@Nullable
	@Override
	public IBinder onBind(Intent intent) {
    	return testStub;
	}
	
	private final ITestService.Stub testStub = new ITestService.Stub() {
		
		... 省略其他方法实现 ...
		
		@Override
    	public void registerCallBack(ITestCallback callback) throws RemoteException {
        	CallbackRegisterManager.getInstance().register(callback);
    	}

    	@Override
    	public void unregisterCallBack(ITestCallback callback) throws RemoteException {
        	CallbackRegisterManager.getInstance().unRegister(callback);
    	}	
	}

}

CallbackRegisterManager的实现,封装下RemoteCallbackList,主要用来管理AIDL接口回调注册的管理

复制代码
	public class CallbackRegisterManager {

	private static CallbackRegisterManager instance;

	private RemoteCallbackList<ITestCallback> remoteCallbackList = null;

	private CallbackRegisterManager() {
    	remoteCallbackList = new RemoteCallbackList<>();
	}

	public static CallbackRegisterManager getInstance() {
    	synchronized (CallbackRegisterManager.class) {
        	if (instance == null) {
            	instance = new CallbackRegisterManager();
        	}
    	}
    	return instance;
	}

	public RemoteCallbackList<ITestCallback> getRemoteCallbackList(){
    	return remoteCallbackList;
	}

	public void register(ITestCallback callback){
    	if(remoteCallbackList != null){
        	remoteCallbackList.register(callback);
    	}
	}

	public void unRegister(ITestCallback callback){
    	if(remoteCallbackList != null){
        	remoteCallbackList.unregister(callback);
    	}
	}
}

放在系统下编译

首先把上面编写好的app源码打包成apk

AS里Build Variants 选 release ,然后 Build -> Build Bundle(s) / APK(s) -> Build APK(s) ,打好的未签名的包在 build 目录 outputs 中,拷贝出apk 文件,修改自定义的名字 ,例如TestServer.apk

在自己源码三方app或者系统app编译的地方,新建脚本LOCAL_MODULE同名文件夹 /TestServer ,然后将如下内容 Android.mk 脚本和TestServer.apk一起放入

让这个新增的app参与系统编译,不同平台略有不同,不在赘述。

复制代码
LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := TestServer

LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_PRIVILEGED_MODULE := true

include $(BUILD_PREBUILT)

> 客户端

由于提供三方app使用,需要将客户端包装成 Library,所以新建 项目 选择 Android Library类型

将服务端的AIDL 原封不动的拷贝到 项目里 ,即 java同级目录aidl目录下

注意: 包名路径和内部方法名称入参等需要和服务端完全一致,不然后面调用就会出现问题,所以直接拷贝过来,简单安全

开放给外部的管理器的实现

复制代码
public class TestManager {
	private Context mContext;
	private static TestManager mInstance;

	private ITestService mService;
	private IBinder b;
	private boolean connected;
	private TestCallbackImpl callbackImpl;
	private ICustomCallback icustomCallback;
	
	private TestManager(Context context, ICustomCallback iCallback) {
    	mContext = context.getApplicationContext();
    	this.icustomCallback = iCallback;
    	// 链接AIDL远程服务
    	bindRemoteService(conn);
	}

	private void bindRemoteService(ServiceConnection serviceConnection) {
    	Intent intent = new Intent();
    	// 指定包名 ,指向我们那个前面做服务的做进系统服务的app的包名
    	intent.setPackage("com.xxx.server");
    	// // 这里的action 即 为 前面我们注册服务自定义的action
    	intent.setAction("action.com.xxx.server"); 
    	mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
	}
	
	// 注册获取管理对象,同时传入三方客户调用端接收远程服务回调的接口
    public static synchronized TestManager register(@NonNull Context context, ICustomCallback icustomCallback) {
    	if (mInstance == null) {
       		mInstance = new TestManager(context, iCallback);
    	}
    	return mInstance;
	}
	
	public void unRegister() {
    	if (connected) {
        	try {
            	if (callbackImpl != null) {
                	getService().unregisterCallBack(callbackImpl);
            	}
        	} catch (Exception e) {
            	e.printStackTrace();
        	}
        	mContext.unbindService(conn);
    	}
	}
	
	// 获取服务对象
	private ITestService getService() {
    	if (mService != null) {
        	return mService;
    	}
    	mService = ITestService.Stub.asInterface(b);
    	return mService;
	}

	private ServiceConnection conn = new ServiceConnection() {

    	@Override
    	public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        	b = iBinder;
        	connected = true;
        	// 初始化aidl远程回调
        	callbackImpl = new TestCallbackImpl(icustomCallback);
        	// 注册aidl远程回调
        	try {
            	getService().registerCallBack(callbackImpl);
        	} catch (RemoteException e) {
            	e.printStackTrace();
        	}
    	}

    	@Override
    	public void onServiceDisconnected(ComponentName componentName) {
        	connected = false;
    	}
	};
	
	// 这里可以做成扔出自定义异常,提醒调用端
	private void isServiceConnected(String opt) {
    	if (!connected) {
        	Log.e(TAG, "opt: " + opt + " -- service not connected!");
    	}
	}

	//  下面便是对上层直接开放的方法 //
	
	public int getScreenBrightness() {
    	isServiceConnected("getScreenBrightness");
    	int result = -1;
    	try {
        	result = getService().getScreenBrightness();
    	} catch (RemoteException e) {
        	e.printStackTrace();
    	}
    	return result;
	}

	public boolean setScreenBrightness(int brightness) {
    	isServiceConnected("setScreenBrightness");
    	boolean optResult = true;
    	try {
        	optResult = getService().setScreenBrightness(brightness);
    	} catch (RemoteException e) {
        	optResult = false;
        	e.printStackTrace();
    	}
    	 return optResult;
    }

}

下面是服务端回调接口 ITestCallback 的实现,入参 ICustomCallback为三方app调用端去实现的接收服务回调数据的接口

复制代码
	public class TestCallbackImpl extends ITestCallback.Stub {

    private ICustomCallback ICustomCallback = null;

	public TestCallbackImpl(ICustomCallback ICustomCallback) {
    	this.iCallback = iCustomCallback;
	}

	@Override
	public void onRemoteCallback(String msg) throws RemoteException {
   
   	 if (ICustomCallback != null) {
       	 ICustomCallback.onCustomCallback(msg);
    	}
	}
}

客户端ICustomCallback回调的接口实现,也很简单

复制代码
public interface ICustomCallback {

	void  onCustomCallback(String msg);
}

至此,这个提供给上层app 使用的封装操作的Library module项目基本已经完成,然后将它编译打包,获取他的aar库包,给到任何客户端即可使用

三方app端调用

复制代码
	private TestManager testManager;
	
    @Override
	protected void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	setContentView(R.layout.activity_main);

    	testManager = TestManager.register(this, myCustomCallback);

	}

	@Override
	protected void onDestroy() {
    	super.onDestroy();
    	if (testManager != null) {
        	testManager.unRegister();
    	}
	}

	ICustomCallback myCustomCallback = new ICustomCallback() {
    	@Override
    	public void onCustomCallback(String msg) {
        	// 做相应操作
    	}
	};
相关推荐
后端码匠2 小时前
MySQL 8.0安装(压缩包方式)
android·mysql·adb
梓仁沐白4 小时前
Android清单文件
android
董可伦6 小时前
Dinky 安装部署并配置提交 Flink Yarn 任务
android·adb·flink
每次的天空7 小时前
Android学习总结之Glide自定义三级缓存(面试篇)
android·学习·glide
恋猫de小郭7 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin
flying robot9 小时前
小结:Android系统架构
android·系统架构
xiaogai_gai9 小时前
有效的聚水潭数据集成到MySQL案例
android·数据库·mysql
鹅鹅鹅呢9 小时前
mysql 登录报错:ERROR 1045(28000):Access denied for user ‘root‘@‘localhost‘ (using password Yes)
android·数据库·mysql
在人间负债^9 小时前
假装自己是个小白 ---- 重新认识MySQL
android·数据库·mysql
Unity官方开发者社区10 小时前
Android App View——团结引擎车机版实现安卓应用原生嵌入 3D 开发场景
android·3d·团结引擎1.5·团结引擎车机版