Android上hook AMS和PMS

好吧,我承认,其实这一篇文章,主要使用到的就是动态代理,但是个人觉得还是有很大意义的,比如说可以降低代码耦合度,如果想在用户的某一类操作都要打印log获取当前参数,或者是记录用户的点击事件,点击时间等,那么此时在现有代码的基础上每次在点击事件中做处理,肯定是可以的,但是这样,我们要修改多少代码,其实此时,我们就可以完全使用代理来实现类似的功能。说道这里了,就先来看看,什么是代理吧

##静态代理
比如,现在都有海外代购,那么这些代购就可以称之为代理,我们不用和真正卖家沟通,只需要借助代理去实现就ok了,但是添加没有免费的午餐,作为代理工作人员,一定是要从中获利的,下面我们就来模拟这一过程。
###定义购买接口

/**
 * BuySomething接口用来购买东西的,实现类和代理类需要实现它
 * @author liuhang
 *
 */
public interface BuySomething {

	public void buyGood(Good good);
}

###添加购买实现类

/**
 * 购买买东西的实现类
 * @author liuhang
 *
 */
public class BuySomethingImpl implements BuySomething {
	@Override
	public void buyGood(Good good) {
		System.out.println("购买了 :"+good.getName()+"  花费 :"+good.getPrice() +"$");
	}
	
}

###购买东西的代理类

/**
 * 购买东西的代理类
 * @author liuhang
 *
 */
public class BuyProxy implements BuySomething {

	private BuySomething buy;

	public BuyProxy(BuySomething buy) {
		super();
		this.buy = buy;
	}


	/**
	 * 作为代理,需要从中抽成,这里每件商品赚取30$
	 */
	@Override
	public void buyGood(Good good) {
		System.out.println("作为BuyProxy, 需要给我打折.....");
		good.setPrice(good.getPrice() - 30);
		buy.buyGood(good);
	}
    public static void main(String[] args) {

		// 将需要购买的东西放到集合中
		List goodList = new ArrayList<>();
		Good iphone = new Good("iphone",5000);
		Good ipad = new Good("ipad",2000);
		goodList.add(iphone);
		goodList.add(ipad);
        // 通过代理购买东西,最终是通过BuySomethingImpl进行购买的
		BuySomething buySomething = new BuyProxy(new BuySomethingImpl());
		for (Good good : goodList) {
			buySomething.buyGood(good);
		}
    }

}

这里写图片描述
可以看到,此时我们购买iphone和ipad实际上不是直接通过BuySomethingImpl去购买的,而是通过BuyProxy,并且BuyProxy也赚取了60大洋呢。 那么代理有什么好处,说一种情况吧,大家应该都在APP中使用过第三方sdk,如果直接在代码中使用,那么如果sdk升级,以后接口参数变了,此时如果我们代码中很多地方使用到了这个方法,
那么糟糕了,难道每个地方都要更改,其实如果使用了代理,不管sdk怎么更改,我们只需要和代理打交道,具体只需要更改代理类就行了。

##动态代理
在java中实现动态代理有两种方法,一种是jdk提供的,另一种是使用cglib实现
###基于jdk实现动态代理###
下面我们使用jdk提供的Proxy,同样实现上面的功能。
####定义代理类DynamicProxy实现InvocationHandler接口

/**
 * 基于JDK实现动态代理,需要实现InvocationHandler接口
 * @author liuhang
 *
 */
public class DynamicProxy implements InvocationHandler {

	private BuySomething mBuySomething;
	private List goodList;
	
	public DynamicProxy(BuySomething mBuySomething, List goodList) {
		super();
		this.mBuySomething = mBuySomething;
		this.goodList = goodList;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		for (Good good : goodList) {
			System.out.println("作为BuyProxy, 需要给我打折.....");
			good.setPrice(good.getPrice() - 30);
			mBuySomething.buyGood(good);
		}
		return null;
	}

public static void main(String[] args) {

		// 将需要购买的东西放到集合中
		List goodList = new ArrayList<>();
		Good iphone = new Good("iphone",5000);
		Good ipad = new Good("ipad",2000);
		goodList.add(iphone);
		goodList.add(ipad);
		
		BuySomething buySomething = (BuySomething) Proxy.newProxyInstance(BuySomething.class.getClassLoader(),new Class[]{BuySomething.class},new DynamicProxy(new BuySomethingImpl(),goodList));
		buySomething.buyGood(null);
}

}

###使用cglib动态代理
下面使用cglib动态代理实现上面的需求,这里需要注意一点,就是一定要引入正确的jar包

public class BuySomeThingCglib implements MethodInterceptor {

	private Enhancer enhancer = new Enhancer();  
	
	/**
	 * 利用Enhancer生成代理类
	 * @param clazz
	 * @return
	 */
	public Object getProxy(Class clazz){  
		//设置需要创建子类的类  
		enhancer.setSuperclass(clazz);  
		enhancer.setCallback(this);  
		//通过字节码技术动态创建子类实例  
		return enhancer.create();  
	} 


	@Override
	public Object intercept(Object obj, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
		System.out.println("作为BuyProxy, 需要给我打折.....");
		methodProxy.invokeSuper(obj, arg);  
		return null;
	}

      public static void main(String[] args) {

		// 将需要购买的东西放到集合中
		List goodList = new ArrayList<>();
		Good iphone = new Good("iphone",5000);
		Good ipad = new Good("ipad",2000);
		goodList.add(iphone);
		goodList.add(ipad);

		BuySomeThingCglib buyCglib = new BuySomeThingCglib();  
		BuySomething buySomething = (BuySomethingImpl)buyCglib.getProxy(BuySomethingImpl.class);  
		for (Good good : goodList) {
			buySomething.buyGood(good);
		}
	}
}

好了,cglib动态代理同样实现上面的需求,这里不做过多的描述,毕竟不是这篇重点。

##Hook activity启动
我们都知道启动activity的时候,最终是通过Instrumentation#execStartActivity 方法启动activity的,具体可以参考 Activity启动流程
###Instrumentation#execStartActivity

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        ........

        try {
            // 通过ActivityManagerNative.getDefault()的startActivity来启动activity
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            // 根据返回的result结果,给用户对应的提示
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
}

###ActivityManagerNative

public abstract class ActivityManagerNative extends Binder implements IActivityManager
{

    static public IActivityManager getDefault() {
        return gDefault.get();
    }



    private static final Singleton gDefault = new Singleton() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
}

这里可以看到ActivityManagerNative.getDefault()内部直接返回gDefault.get(),其中gDefault是Singleton泛型类

public abstract class Singleton {
    private T mInstance;

    protected abstract T create();

    // get方法返回对应的泛型类,这里是IActivityManager
    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

揭开gDefault.get()的面纱,我们知道其中在Singleton中维护了一个mInstance变量,并且gDefault.get()方法最终返回的就是mInstance对应的泛型类实例,也就是说通过ContextImpl#startActivity方法启动activity的时候,最终是通过mInstance也就是IActivityManager来启动的,其实就是ActivityManagerService,所以我们要做的就是通过反射对mInstance重新赋值,将我们自己的代理类赋值给mInstance,然后在代理类中根据系统获取的IActivityManager在执行操作,所以这里我们就可以在对应的操作前后,进行一些记录或者关键log的打印.
好了,废话不多说,先看代码吧

  • 创建一个代理类,需要实现InvocationHandler接口,用来代理IActivityManager的操作
public class AMSProxy implements InvocationHandler {

    private static final String TAG = "HookAMS";

    private Object iActivityManager;

    public AMSProxy(Object iActivityManager) {
        this.iActivityManager = iActivityManager;
    }

    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        Log.d(TAG, "method name is :" + method.getName() + " args length is :" + args.length + "   args is :" + args);
        if ("startActivity".equals(method.getName())) {
            // 第三个参数是intent
            Intent intent = (Intent)args[2];
            Log.d(TAG, "method name is :"+ method.getName()+"   intent is :"+intent+"   extradata is :"+intent.getStringExtra("DATA"));
        }
        return method.invoke(iActivityManager, args);
    }

}
  • 开始hook AMS
Class activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNativeClazz.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null); // 获取gDefault实例,由于是静态类型的属性,所以这里直接传递null参数

// 下面通过反射执行gDefault.get();操作,最终返回IActivityManager,也就是ActivityManagerService的实例
Class singleTonClazz = Class.forName("android.util.Singleton");
Field mInstanceField = singleTonClazz.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object iActivityManager = mInstanceField.get(gDefault);

Class iActivityManagerClazz = Class.forName("android.app.IActivityManager");
// 指定被代理对象的类加载器
// 指定被代理对象所实现的接口,这里就是代理IActivityManager
// 表示这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
Object myProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iActivityManagerClazz}, new AMSProxy(iActivityManager));
mInstanceField.set(gDefault,myProxy);

此时运行我们的程序,启动activity,就可以看到,每次在启动activity之前都会打印相关的参数,其实不只是启动activity,启动service也可以,只要最终是通过ActivityManagerNative来操作的都可以拦截。

##Hook AMS##
有了上面的基础,Hook AMS就可以照猫画虎了,其实主要是要找到一个HOOK点,一般来说 静态变量和静态方法是很好的点,因为反射的时候,我们不用构造对象来获取。好了进入正轨吧,在Android中所有的关于PMS的操作,其实我们都是通过PackageManager操作的,那么就从PackageManager的获取开始分析吧:
###ContextImpl#getPackageManager

@Override
public PackageManager getPackageManager() {
        if (mPackageManager != null) {
            return mPackageManager;
        }
        // 通过ActivityThread获取IPackageManager,bingo,ActivityThread#getPackageManager是一个静态方法
        IPackageManager pm = ActivityThread.getPackageManager();
        if (pm != null) {
            // 这里我们还需要替换pm为自己的代理
            return (mPackageManager = new ApplicationPackageManager(this, pm));
        }

        return null;
}

###ApplicationPackageManager

final class ApplicationPackageManager extends PackageManager {

   
   ApplicationPackageManager(ContextImpl context,
                              IPackageManager pm) {
        mContext = context;
        mPM = pm;
    }
}

###ActivityThread#getPackageManager###

public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            return sPackageManager;
        }
        IBinder b = ServiceManager.getService("package");
        sPackageManager = IPackageManager.Stub.asInterface(b);
        return sPackageManager;
}

###ActivityThread#currentActivityThread###

public static ActivityThread currentActivityThread() {
        return sCurrentActivityThread;
}

currentActivityThread也是一个静态方法,所以可以通过反射直接获取ActivityThread实例
可以看到,上面最终返回的是sPackageManager实例,所以这里hook点也一目了然,所以我们只需要按照下面步骤进行即可:

  1. 获取ActivityThread类中sPackageManager静态属性的值
  2. 创建一个代理,然后将sPackageManager传递进入代理
  3. 最后重新为当前的ActivityThread对象设置sPackageManager,此时设置为我们的代理类即可
  4. 替换ApplicationPackageManager中的mPM对象,此时设置为我们的代理类即可
// 1.获取ActivityThread类中sPackageManager静态属性的值
Class activityThreadClazz = Class.forName("android.app.ActivityThread");

// 1.1 获取当前ActivityThread实例
Method currentActivityThreadMethod = activityThreadClazz.getDeclaredMethod("currentActivityThread");
Object activityThreadObj = currentActivityThreadMethod.invoke(null);

// 1.2 获取sPackageManager静态属性的值
Field sPackageManagerField = activityThreadClazz.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManagerObj = sPackageManagerField.get(activityThreadObj);

// 2. 创建一个代理,然后将sPackageManager传递进入代理
Class iPackageManagerClazz = Class.forName("android.content.pm.IPackageManager");
Object pmsProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{iPackageManagerClazz},new PMSProxy(sPackageManagerObj));

// 3. 重新为当前的ActivityThread对象设置sPackageManager
sPackageManagerField.set(activityThreadObj,pmsProxy);

// 4. 替换ApplicationPackageManager中的mPM对象
PackageManager packageManager = getPackageManager();
Class applicationPackageManagerClazz = Class.forName("android.app.ApplicationPackageManager");
Field mPMField = applicationPackageManagerClazz.getDeclaredField("mPM");
mPMField.setAccessible(true);
mPMField.set(packageManager,pmsProxy);


此时如果我们使用PMS的API调用,就会先被我们的代理拦截,执行自己的log打印,然后才做对应的操作需要注意的是,我们hook AMS和PMS的代码
,由于是需要针对整个应用的,所以必须越早执行越好,这里我把他放在自定义的Application的onCreate方法去执行

##hook点击事件
很多时候我们需要在不修改系统代码的情况下,对当前用户的操作进行记录和统计,比如用户点击的是哪个view,什么时间点击的等等,此时同样可以通过反射和代理来实现对应的功能,这个和Spring中的AOP的实现其实是一样的,下面我举一个Click button的栗子。

###点击事件分析###
平时为button设置点击事件是通过setOnClickListener来实现的

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true); // 设置当前view可以被点击
        }
        getListenerInfo().mOnClickListener = l;
}

ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
}

最终点击事件是通过 方法执行的

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this); // 点击事件操作
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
}

可以看到这里其实是将我们设置的listener赋值给了mOnClickListener, 所以思路和上面的启动activity一样,我们需要获取系统已有的(其实就是我们设置的OnClickListener)mOnClickListener,然后使用自己的代理来实现该需求。mOnClickListener是ListenerInfo中的一个属性,其实在ListenerInfo中有很多 用户点击,长按等事件的监听

static class ListenerInfo {
        protected OnFocusChangeListener mOnFocusChangeListener;
        private ArrayList mOnLayoutChangeListeners;
        protected OnScrollChangeListener mOnScrollChangeListener;
        private CopyOnWriteArrayList mOnAttachStateChangeListeners;
        public OnClickListener mOnClickListener;
        protected OnLongClickListener mOnLongClickListener;
        protected OnContextClickListener mOnContextClickListener;
        protected OnCreateContextMenuListener mOnCreateContextMenuListener;

        private OnKeyListener mOnKeyListener;

        private OnTouchListener mOnTouchListener;

        private OnHoverListener mOnHoverListener;

        private OnGenericMotionListener mOnGenericMotionListener;

        private OnDragListener mOnDragListener;

        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}

所以我们需要做的就是有如下几步:

  1. 通过反射获取ListenerInfo对象
  2. 通过ListenerInfo获取mOnClickListener对象
  3. 创建代理类,传入onClickListener对象
  4. 重新设置系统的mOnClickListener为我们自己的代理类

###hook单个View的点击事件###
有了上面的分析和基础,下面hook单个view的点击事件就是顺理成章的事情了。

private  void hookOnClick(T view) {
        try {
            // 通过反射获取ListenerInfo对象
            Class viewClazz = Class.forName("android.view.View");
            Method getListenerInfoMethod = viewClazz.getDeclaredMethod("getListenerInfo");
            getListenerInfoMethod.setAccessible(true);
            Object listenerInfo = getListenerInfoMethod.invoke(view);

	    // 通过ListenerInfo获取mOnClickListener对象
            Class mClassListenerInfo = Class.forName("android.view.View$ListenerInfo");
            Class listenerInfoClazz = Class.forName("android.view.View$ListenerInfo");
            Field mOnClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener");
            mOnClickListenerField.setAccessible(true);
            View.OnClickListener onClickListener =  (View.OnClickListener)mOnClickListenerField.get(listenerInfo);
            
            if (onClickListener == null) { // 若onClickListener == null,表明当前view没有设置点击事件
                return;
            }

            // 创建代理类,传入onClickListener对象
            OnClickListenerProxy clickListenerProxy = new OnClickListenerProxy(onClickListener);
            // 重新设置系统的mOnClickListener为我们自己的代理类
            mOnClickListenerField.set(listenerInfo,clickListenerProxy);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
}

###hook整个activity的点击事件###
以上方法是对于单个view的点击事件代理的,那么对于一个activity来讲,我们需要按下面步骤进行处理

  1. 创建一个BaseActivity,使我们的activity继承自它
  2. 遍历当前activity中的所有子元素,对于每一个view执行hookOnClick方法,此时如果当前view设置了OnClickListener,那么就会按照我们的代理类来执行获取点击操作,否则不做任何操作
  3. 提供获取HookViewClick的接口
  4. 在BaseActivity的onWindowFocusChanged方法中,hook当前activity所有view的点击事件

####获取Activity的所有View####

/**
 * @note 获取该activity所有view
 * @author liuh
 * */
private List getAllChildViews(Activity activity) {
        View view = activity.getWindow().getDecorView();
        return getAllChildViews(view);
}

private List getAllChildViews(View view) {
        List allchildren = new ArrayList();
        if (view instanceof ViewGroup) {
            ViewGroup vp = (ViewGroup) view;
            for (int i = 0; i < vp.getChildCount(); i++) {
                View viewchild = vp.getChildAt(i);
                allchildren.add(viewchild);
                allchildren.addAll(getAllChildViews(viewchild));
            }
        }
        return allchildren;
}

####遍历所有View####

// 遍历所有的View
public void hookViews(Activity activity) {
        List views = getAllChildViews(activity);
        for (View view : views) {
            hookOnClick(view);
        }
}

####提供获取HookViewClick的接口####

public static HookViewClick getInstance() {
       if (mHookViewClick == null) {
           synchronized (HookViewClick.class) {
                if (mHookViewClick == null) {
                    mHookViewClick = new HookViewClick();
                }
           }
       }
        return mHookViewClick;
}


####hook当前activity所有view的点击事件####

public class BaseActivity extends AppCompatActivity {
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        HookViewClick.getInstance().hookViews(this);
    }
}

好了,到此为止,我们如果需要搜集用户的点击事件相关参数,只需要继承我们的BaseActivity即可,而不需要对当前的代码做过多的修改。

源代码下载

欢 迎 关 注 我 的 公 众 号 “编 程 大 全”

专注技术分享,包括Java,python,AI人工智能,Android分享,不定期更新学习视频

你可能感兴趣的:(android,framework,Android,Framework)