[关闭]
@TryLoveCatch 2021-04-07T21:25:31.000000Z 字数 3799 阅读 1392

Android基础之实际项目问题

android android基础 项目问题


setBackground()之后padding无效的问题

设置Background导致Padding无效问题追溯

问题描述

在RecyclerView的itemView里面,xml直接设置背景,和在代码里面setBackground(),效果竟然不是一样的。

布局文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <com.xxx.view.CardContainer xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:layout_margin="@dimen/dp_4.5"
  6. android:background="@drawable/selector_item_movie"
  7. android:focusable="true"
  8. android:focusableInTouchMode="false"
  9. android:gravity="center"
  10. android:padding="@dimen/dp_3"
  11. android:id="@+id/cardContainer">
  12. <RelativeLayout
  13. android:layout_width="match_parent"
  14. android:layout_height="match_parent"
  15. android:gravity="center">
  16. <ImageView
  17. android:id="@+id/card_imageview"
  18. android:layout_width="match_parent"
  19. android:layout_height="match_parent"
  20. android:background="@color/default_image_color"
  21. android:scaleType="centerCrop" />
  22. ....
  23. </RelativeLayout>
  24. </com.xxx.view.CardContainer>

selector_item_movie

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <!--item 背景文件-->
  4. <item android:state_focused="true" android:drawable="@drawable/stroke" />
  5. </selector>

@drawable/stroke

位置模拟调研

开源组件DoraemonKit之Android版本技术实现

位置模拟是地图类应用十分常用的调试功能,哆啦A梦在实现位置模拟功能时主要尝试了两种方案。

方案一

第一个方案是Android系统提供的LocationManager类下面TestProvider相关API,这个方案的实现非常容易,只需要调用相关的系统API。

它mock的不仅限于应用本身,也可以影响到其他应用,所以很多位置模拟软件都是使用这个方案实现的。

缺点

方案二

第二个方案是通过Hook系统Binder服务的方式,动态代理Location Service。

  1. public class LocationHookHandler implements InvocationHandler {
  2. private static final String TAG = "LocationHookHandler";
  3. private Object mOriginService;
  4. @SuppressWarnings("unchecked")
  5. @SuppressLint("PrivateApi")
  6. public LocationHookHandler(IBinder binder) {
  7. try {
  8. Class iLocationManager$Stub = Class.forName("android.location.ILocationManager$Stub");
  9. Method asInterface = iLocationManager$Stub.getDeclaredMethod("asInterface", IBinder.class);
  10. this.mOriginService = asInterface.invoke(null, binder);
  11. } catch (Exception e) {
  12. LogHelper.e(TAG, e.toString());
  13. }
  14. }
  15. @Override
  16. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  17. switch (method.getName()) {
  18. case "requestLocationUpdates":
  19. ...
  20. break;
  21. case "getLastLocation":
  22. ...
  23. return lastLocation;
  24. case "getLastKnownLocation":
  25. ...
  26. return lastKnownLocation;
  27. default:
  28. break;
  29. }
  30. return method.invoke(this.mOriginService, args);
  31. }
  32. }

上面的代码就是Location服务的代理类,通过替换原有requestLocationUpdates、getLastLocation和getLastKnownLocation接口的实现就可以实现模拟定位,返回我们想要模拟的位置,主要利用的就是InvocationHandler动态代理机制。

Android对系统服务主要是通过ServiceManager去管理的,且服务的实例是保存在静态全局变量中的。

  1. public final class ServiceManager {
  2. private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
  3. ...
  4. public static IBinder getService(String name) {
  5. try {
  6. IBinder service = sCache.get(name);
  7. if (service != null) {
  8. return service;
  9. } else {
  10. return Binder.allowBlocking(getIServiceManager().getService(name));
  11. }
  12. } catch (RemoteException e) {
  13. Log.e(TAG, "error in getService", e);
  14. }
  15. return null;
  16. }
  17. ...

服务实例保存在HashMap中,key是Context中定义的常量。
所以可以在应用初始化的时候提前替换掉sCache中的实例,这样后面通过context.getSystemService获取到的Service实例就是被动态代理的实例.

  1. Class serviceManager = Class.forName("android.os.ServiceManager");
  2. Method getService = serviceManager.getDeclaredMethod("getService", String.class);
  3. IBinder binder = (IBinder) getService.invoke(null, Context.LOCATION_SERVICE);
  4. ClassLoader classLoader = binder.getClass().getClassLoader();
  5. Class[] interfaces = {IBinder.class};
  6. BinderHookHandler handler = new BinderHookHandler(binder);
  7. IBinder proxy = (IBinder) Proxy.newProxyInstance(classLoader, interfaces, handler);
  8. Field sCache = serviceManager.getDeclaredField("sCache");
  9. sCache.setAccessible(true);
  10. Map<String, IBinder> cache = (Map<String, IBinder>) sCache.get(null);
  11. cache.put(Context.LOCATION_SERVICE, proxy);
  12. sCache.setAccessible(false);

替换实例的时机需要尽可能早,这样才能保证在context.getSystemService前替换掉对应实例,所以在应用初始化的时机执行替换是比较推荐的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注