点击上方"蓝字"关注我们!
背景
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);