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) {
        	// 做相应操作
    	}
	};
相关推荐
Calvin8808282 分钟前
Android Studio 的革命性更新:Project Quartz 和 Gemini,开启 AI 开发新时代!
android·人工智能·android studio
敲代码敲到头发茂密1 小时前
【大语言模型】LangChain 核心模块介绍(Memorys)
android·语言模型·langchain
H1002 小时前
重构(二)
android·重构
拓端研究室3 小时前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
zhangphil4 小时前
Android简洁缩放Matrix实现图像马赛克,Kotlin
android·kotlin
m0_512744644 小时前
极客大挑战2024-web-wp(详细)
android·前端
lw向北.4 小时前
Qt For Android之环境搭建(Qt 5.12.11 Qt下载SDK的处理方案)
android·开发语言·qt
不爱学习的啊Biao4 小时前
【13】MySQL如何选择合适的索引?
android·数据库·mysql
Clockwiseee4 小时前
PHP伪协议总结
android·开发语言·php
mmsx11 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库