Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Android上hook AMS和PMS

$
0
0

好吧,我承认,其实这一篇文章,主要使用到的就是动态代理,但是个人觉得还是有很大意义的,比如说可以降低代码耦合度,如果想在用户的某一类操作都要打印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<Good> 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<Good> goodList;

    public DynamicProxy(BuySomething mBuySomething, List<Good> 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<Good> 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<Good> 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<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        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<T> {
    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<OnLayoutChangeListener> mOnLayoutChangeListeners;
        protected OnScrollChangeListener mOnScrollChangeListener;
        private CopyOnWriteArrayList<OnAttachStateChangeListener> 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 <T extends View> 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<View> getAllChildViews(Activity activity) {
        View view = activity.getWindow().getDecorView();
        return getAllChildViews(view);
}

private List<View> getAllChildViews(View view) {
        List<View> allchildren = new ArrayList<View>();
        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<View> 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即可,而不需要对当前的代码做过多的修改。

源代码下载

作者:mockingbirds 发表于2017/1/2 10:39:59 原文链接
阅读:106 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>