写点什么

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; }}
复制代码
  1. 在清单文件中声明

<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();
复制代码
  1. 通过反射机制获取 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);}
复制代码
  1. 读取 ContentProviderHolder 的 provider 字段

Field field =holder.getClass().getDeclaredField("provider");field.setAccessible(true);provider = (IContentProvider)field.get(holder);
复制代码
  1. 调用 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);
复制代码

更多学习资料戳下方!!!

https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=infoQ&timestamp=1662366626&author=xueqi

用户头像

社区:ceshiren.com 2022.08.29 加入

微信公众号:霍格沃兹测试开发 提供性能测试、自动化测试、测试开发等资料、实事更新一线互联网大厂测试岗位内推需求,共享测试行业动态及资讯,更可零距离接触众多业内大佬

评论

发布
暂无评论
Shell 进程通过 ContentProvider 实现跨进程通信_测试_测吧(北京)科技有限公司_InfoQ写作社区