Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android插件化開發之Hook StartActivity方法

Android插件化開發之Hook StartActivity方法

編輯:關於Android編程

第一步、先爆項目demo照片,代碼不多,不要怕

\  

第二步、應該知道Java反射相關知識

如果不知道或者忘記的小伙伴請猛搓這裡,Android插件化開發基礎之Java反射機制研究 
 

第三步、應該知道Java靜態代理知識

如果不知道或者忘記的小伙伴請猛搓這裡,Android插件化開發基礎之靜態代理模式

第四部、應該知道Java動態代理知識

如果不知道或者忘記的小伙伴請猛搓這裡,Android插件化開發基礎之Java動態代理(proxy)機制的簡單例子  上面只有代碼的解釋,如果不是很清楚的小伙伴可以再去到網上搜一搜相關知識。  

第五步、應該知道Android裡面的ActivityThread類和Instrumentation類

如果不知道或者忘記的小伙伴請猛搓這裡,Android插件化開發之AMS與應用程序(客戶端ActivityThread、Instrumentation、Activity) 希望認真看,知道ActivityThread和Instrumentation是干嘛的,方便下面分析。


第六步、理解hook,並且分析源碼

如果我們自己創建代理對象,然後把原始對象替換為我們的代理對象,那麼就可以在這個代理對象為所欲為了;修改參數,替換返回值,我們稱之為Hook。 接下來我們來實現Hook掉startActivity這個方法,當每次調用這個方法的時候來做我們需要做的事情,我這裡之打印了一些信息,讀者可以更具自己的需求來修改, 我們的目的是攔截startActivity方法,有點類是spring 裡面AOP的思想。  

1、hook一般在哪裡hook?

首先分析我們需要hook哪些對象,也就是說我們要hook的地方,什麼樣的對象比較好Hook呢?一般是容易找到和不容易改變的對象,這思路就來了,不改變一般在我們類的裡面單例對象是唯一對象,靜態變量我們一般加上final static 修飾,有了final就不會改變,不變模式裡面比如String類,裡面就很多final,我們更加這些找到hook的地方。  

2、我們hook startActivity,分析startActivity到底是怎麼實現的

我們每次Context.startActivity,由於Context的實現實際上是ContextImpl來實現的,;我們看ConetxtImpl類的startActivity方法:  
@Override
public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();
    if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
        throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity "
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
    }
    mMainThread.getInstrumentation().execStartActivity(
        getOuterContext(), mMainThread.getApplicationThread(), null,
        (Activity)null, intent, -1, options);
}

我們可以看到startActivity方法最後還是通過Instrumentation對象來執行execStartActivity來實現的,如果你認真看了《Android插件化開發之AMS與應用程序(客戶端ActivityThread、Instrumentation、Activity)通信模型分析 》上面這篇博客,我們知道ActivityThread就是主線程,也就是我們常說的UI線程,可以去更新UI,一個進程只有一個主線程,我們可以在這裡hook.
我們需要Hook掉我們的主線程對象,把主線程對象裡面的mInstrumentation給替換成我們修改過的代理對象;要替換主線程對象裡面的字段

1)首先我們得拿到主線程對象的引用,如何獲取呢?ActivityThread類裡面有一個靜態方法currentActivityThread可以幫助我們拿到這個對象類;但是ActivityThread是一個隱藏類,我們需要用反射去獲取,代碼如下:

// 先獲取到當前的ActivityThread對象
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);


2)拿到這個currentActivityThread之後,我們需要修改它的mInstrumentation這個字段為我們的代理對象,我們先實現這個代理對象,由於JDK動態代理只支持接口,而這個Instrumentation是一個類,我們可以手動寫靜態代理類,覆蓋掉原始的方法,代碼如下
package com.example.hookstartactivity;

import java.lang.reflect.Method;

import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityResult;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;


public class InstrumentationProxy extends Instrumentation {


	 public static final String TAG = "InstrumentationProxy";
	 public static final String EXEC_START_ACTIVITY = "execStartActivity";
	 
	 // ActivityThread裡面原始的Instrumentation對象,這裡千萬不能寫成mInstrumentation,這樣寫
	 //拋出異常,已親測試,所以這個地方就要注意了
	 public Instrumentation oldInstrumentation;
     
	 //通過構造函數來傳遞對象
	 public InstrumentationProxy(Instrumentation mInstrumentation) {
		 oldInstrumentation = mInstrumentation;
	 }


	 //這個方法是由於原始方法裡面的Instrumentation有execStartActivity方法來定的
	 public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
			 Intent intent, int requestCode, Bundle options) {
		 Log.d(TAG, "\n打印調用startActivity相關參數: \n" + "who = [" + who + "], " +
			  "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
	          "\ntarget = [" + target + "], \nintent = [" + intent +
	          "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");


		Log.i(TAG, "------------hook  success------------->");
		Log.i(TAG, "這裡可以做你在打開StartActivity方法之前的事情");
		Log.i(TAG, "------------hook  success------------->");
		Log.i(TAG, "");
			
		//由於這個方法是隱藏的,所以需要反射來調用,先找到這方法
	    try {
	    	Method execStartActivity = Instrumentation.class.getDeclaredMethod(
	            	EXEC_START_ACTIVITY,
	                Context.class, IBinder.class, IBinder.class, Activity.class, 
	                Intent.class, int.class, Bundle.class);
	        execStartActivity.setAccessible(true);
	        return (ActivityResult) execStartActivity.invoke(oldInstrumentation, who, 
	                    contextThread, token, target, intent, requestCode, options);
	        } catch (Exception e) {
	        	//如果你在這個類的成員變量Instrumentation的實例寫錯mInstrument,代碼講會執行到這裡來
	            throw new RuntimeException("if Instrumentation paramerter is mInstrumentation, hook will fail");
	        }
	 }
}


3)然後用代理對象替換,代碼如下



package com.example.hookstartactivity;


import java.lang.reflect.Field;
import java.lang.reflect.Method;


import android.app.Application;
import android.app.Instrumentation;
import android.util.Log;


public class MyApplication extends Application {


	public static final String TAG = "MyApplication";
	public static final String ACTIVIT_THREAD = "android.app.ActivityThread";
	public static final String CURRENT_ACTIVITY_THREAD = "currentActivityThread";
	public static final String INSTRUMENTATION = "mInstrumentation";
	
	@Override
	public void onCreate() {
		try {
		     //這個方法一般是寫在Application的oncreate函數裡面,如果你寫在activity裡面的oncrate函數裡面就已經晚了
		     attachContext();
		} catch (Exception e) {
		     e.printStackTrace();
		}
	}
	
	public static void attachContext() throws Exception{
		
	    //獲取當前的ActivityThread對象
	    Class activityThreadClass = Class.forName(ACTIVIT_THREAD);
	    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(CURRENT_ACTIVITY_THREAD);
	    currentActivityThreadMethod.setAccessible(true);
	    Object currentActivityThread = currentActivityThreadMethod.invoke(null);


	    //拿到在ActivityThread類裡面的原始mInstrumentation對象
	    Field mInstrumentationField = activityThreadClass.getDeclaredField(INSTRUMENTATION);
	    mInstrumentationField.setAccessible(true);
	    Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);


	    //構建我們的代理對象
	    Instrumentation evilInstrumentation = new InstrumentationProxy(mInstrumentation);
	    
	    //通過反射,換掉字段,注意,這裡是反射的代碼,不是Instrumentation裡面的方法
	    mInstrumentationField.set(currentActivityThread, evilInstrumentation);
	    
	    //做個標記,方便後面查看
	    Log.i(TAG, "has go in MyApplication attachContext method");
	}
}

要注意這個替換要在Application裡面的oncreate方法裡面去執行,如果到Activity方法裡面去執行的話就晚了,程序不會報錯,但是hook不到。


然後我是在主頁面寫了一個按鈕,點擊來觸發startActivity的。
MainActivity.java 文件如下
package com.example.hookstartactivity;


import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;


public class MainActivity extends ActionBarActivity {
    
	public static final String TAG  =  "MainActivity";
	public TextView tv;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		tv = (TextView)findViewById(R.id.start);
		tv.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View v) {
				try {
					Intent intent = new Intent(MainActivity.this, SecondActivity.class); 
					Bundle bundle = new Bundle();
					Log.i(TAG, "-------------------------------->");
					Log.i(TAG, "startActivity before");
					Log.i(TAG, "-------------------------------->");
					
					startActivity(intent, bundle);
					
					Log.i(TAG, "-------------------------------->");
					Log.i(TAG, "startActivity after");
					Log.i(TAG, "-------------------------------->");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} );
	}
}


跳轉到的Second.java文件如下
package com.example.hookstartactivity;


import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;


public class SecondActivity extends ActionBarActivity{
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_second);
	}
}

 

第七步、運行代碼

啟動項目在ubuntu終端打印的日志圖片如下: \     然後我點擊圖標調用startActivity方法後ubuntu終端打印的日志圖片如下: \   日志上面看到,hook success了 看到ubuntu終端日志打印,前面顯示了類,方便通過日志找bug,我用的是pidcat,下載pidcat 然後在ubuntu上面運行
pidcat.py 包名
就可以非常方便看的日志找bug了。  

第八步、總結

通過這篇日志,希望打擊可以更好的理解hook、java反射、靜態代理、動態代理、ActivityThread、Instrumentation 最後附上源碼下載地址,需要的小伙伴請猛搓這裡HookStartActivityDemo    
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved