Shell 进程通过 ContentProvider 实现跨进程通信
- 2022 年 9 月 28 日 北京
本文字数:5347 字
阅读完需:约 18 分钟
背景
Android 系统的 UI 测试框架有 Uiautomator1.0 和 Uiautomator2.0,虽然 Uiautomator1.0 在 Android11 及以后的版本被放弃了,但是我们仍然可以通过反射 FrameWork 代码初的方式始化 Uiautomator1.0 服务,这样我们原有的测试用例就可以继续运行了。
今天分享一下 Uiautomator1.0 与 server app 跨进程通信的方案,我们一般情况下都使用 socket 进行通信,但是当 server app 没有运行时就很难及时处理,我们让 server app 实现 ContentProvider 来对外提供服务,即使 server app 没有运行,我们也能正常调用服务,系统会自动帮我们启动 server app。
在应用(server app)内提供 ContentProvider 服务是很简单的,但是 Uiautomator1.0 和其他 Shell 进程是没有办法直接访问的,我们需要依赖反射技术进行调用。
实现
server app 创建 ContentProvider 服务
package com.xxxx.xxxx.ticker.server;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
/**
* @author walker
* @date 2021/1/29.
* @description 对外提供ContentProvider接口
*/
public class CommonProvider_tme extends ContentProvider {
@Override
public boolean onCreate() {
init();
return false;
}
private void init() {
}
;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
/**
* @author walker
* @Date 2021/3/11
* @Description: 如果需要附加信息,用此字段存储
*/
public static final String RES_DATA = "data";
/**
* @author walker
* @Date 2021/3/11
* @Description: int类型的处理码
* 200 流程处理结束
* 404 指定的方法名错误
* 100 调用服务的认证信息错误
* 500 业务错误
* 501 处理异常
*/
public static final String RES_CODE = "code";
/**
* @author walker
* @Date 2021/3/11
* @Description: 处理结果,提示处理的异常信息等
*/
public static final String RES_INFO = "info";
/**
* @author walker
* @Date 2021/1/29
* @Description: 对外提供call方法,通过方法名来指定调用的功能,是用arg进行简单的参数传递
*/
@Override
public Bundle call(String authority, String method, String arg, Bundle extras) {
Bundle resBundle = new Bundle();
System.out.println("®®® call contentprovider authority=" + authority + "; method=" + method + "; arg=" + arg + "; extras" + extras);
try {
//限定调用的方法
if (method==null || method.trim().length()==0) {
resBundle.putString(RES_DATA, "");
resBundle.putInt(RES_CODE, 404);
resBundle.putString(RES_INFO, "Provider错误:必须指定调用方法名");
return resBundle;
}
//验证访问请求
if (!"com.xxxx.xxxx.xxxx".equals(authority)) {
resBundle.putString(RES_DATA, "");
resBundle.putInt(RES_CODE, 100);
resBundle.putString(RES_INFO, "Provider错误:必须指定有效authorities信息");
return resBundle;
}
resBundle.putString(RES_INFO, "ok");
resBundle.putInt(RES_CODE, 200);
boolean res = true;
switch (method) {
//根据不同的方法实现不同的业务
}
if (!res) {
resBundle.putInt(RES_CODE, 500);
}
}catch (Exception e){
resBundle.putInt(RES_CODE, 501);
resBundle.putString(RES_INFO,e.getMessage());
}
return resBundle;
}
}
在清单文件中声明
<application
...>
<provider android:name=".server.CommonProvider"
android:authorities="com.xxxx.xxxx.xxxx"
android:exported="true"
android:enabled="true"
/>
</application>
在 Shell 进程或 Uiautomator1.0 内访问 CommonProvider
IActivityManager activityManager = (IActivityManager) ActivityManagerNative.getDefault();
通过反射机制获取 ContentProviderHolder
if (Build.VERSION.SDK_INT > 28) {
Method method=activityManager.getClass().getDeclaredMethod("getContentProviderExternal",String.class,int.class, IBinder.class,String.class);
holder=method.invoke(activityManager,authority, USER_SYSTEM, token, null);
} else {
Method method=activityManager.getClass().getDeclaredMethod("getContentProviderExternal",String.class,int.class, IBinder.class);
holder=method.invoke(activityManager,authority, USER_SYSTEM, token);
}
读取 ContentProviderHolder 的 provider 字段
Field field =holder.getClass().getDeclaredField("provider");
field.setAccessible(true);
provider = (IContentProvider)field.get(holder);
调用 prover 服务
**provider:**前面得到的 IContentProvider 对象
**calling_package:**指定任意字符串即可
**attributionTag:**无意义字符串,传递 NULL 即可
**authority:**校验字符串,必须与 server app 内指定的保持一致
**method:**调用的方法名
**arg:**访问字符串参数,一般不用
if (Build.VERSION.SDK_INT >= 30) {
try {
Method method1=provider.getClass().getDeclaredMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
return (Bundle) method1.invoke(provider,calling_package,attributionTag,authority,method,arg,extras);
} catch (Exception e) {
System.err.println(e.getMessage());
}
} else if (Build.VERSION.SDK_INT > 28) {
try {
Method method1=provider.getClass().getDeclaredMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
return (Bundle) method1.invoke(provider,calling_package,authority,method,arg,extras);
} catch (Exception e) {
System.err.println(e.getMessage());
}
} else {
return provider.call(calling_package, method, arg, extras);
}
附完整代码
public class ShellContentProvider {
int USER_SYSTEM = 0;
String calling_package = "com.android.shell";
String authority = "com.xxxx.xxxx.xxxx";
public ShellContentProvider() {
initProvider();
}
IContentProvider provider = null;
/**
* @author walker
* @Date 2021/3/2
* @Description: 初始化当前provider
*/
void initProvider(){
try {
IActivityManager activityManager = (IActivityManager) ActivityManagerNative.getDefault();
final IBinder token = new Binder();
Object holder = null;
try {
if (Build.VERSION.SDK_INT > 28) {
Method method=activityManager.getClass().getDeclaredMethod("getContentProviderExternal",String.class,int.class, IBinder.class,String.class);
holder=method.invoke(activityManager,authority, USER_SYSTEM, token, null);
} else {
Method method=activityManager.getClass().getDeclaredMethod("getContentProviderExternal",String.class,int.class, IBinder.class);
holder=method.invoke(activityManager,authority, USER_SYSTEM, token);
}
if (holder == null) {
throw new IllegalStateException("Could not find provider");
}
Field field =holder.getClass().getDeclaredField("provider");
field.setAccessible(true);
provider = (IContentProvider)field.get(holder);
} finally {
}
} catch (Exception e) {
System.err.println("Error while accessing settings provider");
System.err.println(e.getMessage());
}
}
public Bundle callProvider( final String authority, final String method, final String arg, final Bundle extras){
return callProvider(provider,calling_package,null,authority,method,arg,extras);
}
public Bundle callProvider(final String method, final String arg, final Bundle extras){
return callProvider(provider,calling_package,null,authority,method,arg,extras);
}
/**
* @author walker
* @Date 2020/12/16
* @Description: 调用IContentProvider方法,实现数据的查询和插入;不同的Android版本可能存在兼容问题
* <ul>
* <li><a href="https://sourcegraph.com/github.com/aosp-mirror/platform_frameworks_base@android-11.0.0_r24/-/blob/core/java/android/content/IContentProvider.java#L82">Android 11代码</a></li>
* <li><a href="https://sourcegraph.com/github.com/aosp-mirror/platform_frameworks_base@android-security-10.0.0_r49/-/blob/core/java/android/content/IContentProvider.java#L83">Android 10代码</a></li>
* </ul>
*/
private Bundle callProvider(IContentProvider provider, final String calling_package, final String attributionTag, final String authority, final String method, final String arg, final Bundle extras) {
if(provider==null){
initProvider();
}
try {
if (Build.VERSION.SDK_INT >= 30) {
try {
Method method1=provider.getClass().getDeclaredMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
return (Bundle) method1.invoke(provider,calling_package,attributionTag,authority,method,arg,extras);
} catch (Exception e) {
System.err.println(e.getMessage());
}
} else if (Build.VERSION.SDK_INT > 28) {
try {
Method method1=provider.getClass().getDeclaredMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
return (Bundle) method1.invoke(provider,calling_package,authority,method,arg,extras);
} catch (Exception e) {
System.err.println(e.getMessage());
}
} else {
return provider.call(calling_package, method, arg, extras);
}
} catch (RemoteException e) {
System.err.println("Can't set key " + arg + " for user " + USER_SYSTEM);
}
return null;
}
}
Bundle bundle=new Bundle();
bundle.putBoolean("isCalled",true);
bundle.putInt("callFlag",1);
bundle.putString("arg","test");
Bundle res= new ShellContentProvider().callProvider("testMethod","hello",bundle);
更多学习资料戳下方!!!
测吧(北京)科技有限公司
社区:ceshiren.com 2022.08.29 加入
微信公众号:霍格沃兹测试开发 提供性能测试、自动化测试、测试开发等资料、实事更新一线互联网大厂测试岗位内推需求,共享测试行业动态及资讯,更可零距离接触众多业内大佬
评论