@TryLoveCatch
2021-04-07T21:25:31.000000Z
字数 3799
阅读 1392
android
android基础
项目问题
在RecyclerView的itemView里面,xml直接设置背景,和在代码里面setBackground(),效果竟然不是一样的。
<?xml version="1.0" encoding="utf-8"?>
<com.xxx.view.CardContainer xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/dp_4.5"
android:background="@drawable/selector_item_movie"
android:focusable="true"
android:focusableInTouchMode="false"
android:gravity="center"
android:padding="@dimen/dp_3"
android:id="@+id/cardContainer">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:id="@+id/card_imageview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/default_image_color"
android:scaleType="centerCrop" />
....
</RelativeLayout>
</com.xxx.view.CardContainer>
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--item 背景文件-->
<item android:state_focused="true" android:drawable="@drawable/stroke" />
</selector>
位置模拟是地图类应用十分常用的调试功能,哆啦A梦在实现位置模拟功能时主要尝试了两种方案。
第一个方案是Android系统提供的LocationManager类下面TestProvider相关API,这个方案的实现非常容易,只需要调用相关的系统API。
它mock的不仅限于应用本身,也可以影响到其他应用,所以很多位置模拟软件都是使用这个方案实现的。
location.isFromMockProvider();
第二个方案是通过Hook系统Binder服务的方式,动态代理Location Service。
public class LocationHookHandler implements InvocationHandler {
private static final String TAG = "LocationHookHandler";
private Object mOriginService;
@SuppressWarnings("unchecked")
@SuppressLint("PrivateApi")
public LocationHookHandler(IBinder binder) {
try {
Class iLocationManager$Stub = Class.forName("android.location.ILocationManager$Stub");
Method asInterface = iLocationManager$Stub.getDeclaredMethod("asInterface", IBinder.class);
this.mOriginService = asInterface.invoke(null, binder);
} catch (Exception e) {
LogHelper.e(TAG, e.toString());
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "requestLocationUpdates":
...
break;
case "getLastLocation":
...
return lastLocation;
case "getLastKnownLocation":
...
return lastKnownLocation;
default:
break;
}
return method.invoke(this.mOriginService, args);
}
}
上面的代码就是Location服务的代理类,通过替换原有requestLocationUpdates、getLastLocation和getLastKnownLocation接口的实现就可以实现模拟定位,返回我们想要模拟的位置,主要利用的就是InvocationHandler动态代理机制。
Android对系统服务主要是通过ServiceManager去管理的,且服务的实例是保存在静态全局变量中的。
public final class ServiceManager {
private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
...
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(getIServiceManager().getService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
...
服务实例保存在HashMap中,key是Context中定义的常量。
所以可以在应用初始化的时候提前替换掉sCache中的实例,这样后面通过context.getSystemService获取到的Service实例就是被动态代理的实例.
Class serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getDeclaredMethod("getService", String.class);
IBinder binder = (IBinder) getService.invoke(null, Context.LOCATION_SERVICE);
ClassLoader classLoader = binder.getClass().getClassLoader();
Class[] interfaces = {IBinder.class};
BinderHookHandler handler = new BinderHookHandler(binder);
IBinder proxy = (IBinder) Proxy.newProxyInstance(classLoader, interfaces, handler);
Field sCache = serviceManager.getDeclaredField("sCache");
sCache.setAccessible(true);
Map<String, IBinder> cache = (Map<String, IBinder>) sCache.get(null);
cache.put(Context.LOCATION_SERVICE, proxy);
sCache.setAccessible(false);
替换实例的时机需要尽可能早,这样才能保证在context.getSystemService前替换掉对应实例,所以在应用初始化的时机执行替换是比较推荐的。