需求场景
当上层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">
</application><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>
到这里 ,作为服务端,开放给上层的调用的配置基本就算完整了。但是实际需求,可能还会涉及到 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) {
// 做相应操作
}
};