想直接看更新內容的請點此處
更新,強迫症福音,onActivityResult方法hook到了
問題
Android中,通過startActivityForResult跳轉頁面獲取資料應該不必多說,但是這種所有獲取到的結果都需要到onActivityResult中處理的方式實在令人蛋疼。
試想一下,我們敲著程式碼唱著歌。突然,半路上跳出一群馬匪,讓我們到另一個頁面獲取一點資料,獲取後還不讓在當前程式碼位置處理邏輯,要去onActivityResult新增一個requestCode分支處理結果,處理完才讓回來,等這一切都做完回來難免就會陷入這樣的思考:我是誰,我在哪,我在幹什麼,我剛才寫到哪了……
再想一下,你跟同事的程式碼,跟到一個startActivityForResult,於是不耐煩地ctrl+f找到onActivityResult,發現裡面充斥著大量的requestCode分支,然後突然意識到剛才沒記下requestCode是什麼……
分析問題
問題的根源是所有處理結果的邏輯都要放到onActivityResult中,在裡面根據requestCode作不同處理。而我們渴望的是能在發起startActivityForResult的時候捎帶著把獲取結果後處理的邏輯也傳進去,並能在內部對requestCode判斷好,不用我們再判斷一遍。
解決問題
嘗試一(不完美方式)
新建一個OnResultManager類,用來管理獲取結果的回撥,下面詳細說。
分析問題時說了,我們希望在發起startActivityForResult的時候就指定好處理結果的邏輯,這個簡單,在OnResultManager中建立一個Callback介面,裡面定義一個OnActivityResult方法,引數和Activity的OnActivityResult方法引數完全一樣,在發起start的時候除了intent和requestCode,再傳一個callback進去。而OnresultManager負責控制在系統的onActivityResult觸發時,呼叫對應callback的方法。
下面是OnResultManager的全部程式碼。
public class OnResultManager {
private static final String TAG = "OnResultManager";
//HashMap的key Integer為requestCode
private static WeakHashMap<Activity,HashMap<Integer,Callback>> mCallbacks = new WeakHashMap<>();
private WeakReference<Activity> mActivity;
public OnResultManager(Activity activity) {
mActivity = new WeakReference<Activity>(activity);
}
public void startForResult(Intent intent, int requestCode, Callback callback){
Activity activity = getActivity();
if(activity == null){
return;
}
addCallback(activity,requestCode,callback);
activity.startActivityForResult(intent,requestCode);
}
public void trigger(int requestCode, int resultCode, Intent data){
Log.d(TAG,"----------- trigger");
Activity activity = getActivity();
if(activity == null){
return;
}
Callback callback = findCallback(activity,requestCode);
if(callback != null){
callback.onActivityResult(requestCode,resultCode,data);
}
}
//獲取該activity、該requestCode對應的callback
private Callback findCallback(Activity activity,int requestCode){
HashMap<Integer,Callback> map = mCallbacks.get(activity);
if(map != null){
return map.remove(requestCode);
}
return null;
}
private void addCallback(Activity activity,int requestCode,Callback callback){
HashMap<Integer,Callback> map = mCallbacks.get(activity);
if(map == null){
map = new HashMap<>();
mCallbacks.put(activity,map);
}
map.put(requestCode,callback);
}
private Activity getActivity(){
return mActivity.get();
}
public interface Callback{
void onActivityResult(int requestCode, int resultCode, Intent data);
}
}
複製程式碼
邏輯很簡單,裡面持有一個mActivity,使用弱引用以防止記憶體洩漏,在構造器中為其賦值。還有一個static的WeakHashMap<Activity,HashMap<Integer,Callback>> mCallbacks 用來存放所有的callback,先以activity分,在各個activity中又使用hashmap儲存requestCode和callback的對應關係。
在startForResult時,最終還是呼叫的activity的startActivityForResult,只不過在跳轉頁面之前,把callback存入了mCallbacks中。
而trigger方法則是根據activity和requestCode從mCallbacks中取出對應的callback,呼叫方法。
現在callback的存和取都搞定了,那麼問題來了,什麼時候觸發“取”的操作呢,即trigger方法怎麼觸發呢?答案是在onActivityResult中呼叫,嫌麻煩可以在BaseActivity中呼叫。
使用示例:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
go.setOnClickListener {
val intent = Intent(this,SecondActivity::class.java)
onResultManager.startForResult(intent,REQUEST_CODE,{requestCode: Int, resultCode: Int, data: Intent? ->
if (resultCode == Activity.RESULT_OK){
val text = data?.getStringExtra("text")
Toast.makeText(this,"result -> "+text,Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(this,"canceled",Toast.LENGTH_SHORT).show()
}
})
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
onResultManager.trigger(requestCode,resultCode,data)
}
複製程式碼
可是這樣好蠢啊,你是不是覺得要是不用手動觸發,能自動觸發多好。我也是這麼想的,所以有整整一天我一直在找有什麼辦法能hook到onActivityResult方法,最後hook了Instrumentation,也hook了AMS,但是都對這個onActivityResult無能為力,看原始碼發現好像是在ActivityThread中傳遞的,但是很不幸的是這個ActivityThread沒辦法hook,至少通過簡單的反射和代理沒辦法做到(如果誰有辦法hook到,懇請您能分享出來,我真的特別想知道,我不甘心啊)
更新,強迫症福音,onActivityResult方法hook到了
這裡感謝一下@world_hello的提醒,十分感謝。
之前看到ActivityThread的mH的時候總想著弄個代理繼承它,然後重寫handleMessage方法,來獲取結果資訊,可是卻苦於繼承不了,其實我一直忽視了Handler內部的一個Callback介面,其實完全不用代理。下面詳細講解一下。
先說一下Handler,我們一般使用的時候大多是繼承自Handler,然後重寫handleMessage方法,在裡面處理接收訊息。其實Handler還有個建構函式可以接收一個Handler.Callback(不要和我們自己定義的callback搞混了啊),在Handler.Callback的handleMessage方法中處理訊息。
看一下Handler原始碼:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製程式碼
可以看到,如果mCallback不為null,就會執行mCallback的handleMessage方法。如果這個handleMessage方法的返回值為true,就會直接return,如果為false,就會繼續執行Handler本身的handleMessage方法。
下面開啟Activity原始碼,startActivityForResult方法中有這麼一段
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
複製程式碼
ar是ActivityResult,如果不為null,就呼叫mMainThread的sendActivityResult方法,這個mMainThread就是ActivityThread型別的,所以繼續開啟ActivityThread原始碼,找到這個方法,順著這個方法一直找一直找,這裡中間的方法我就不貼了,最後會找到一個叫sendMessage的方法,這個方法最後一行是
mH.sendMessage(msg);
複製程式碼
這個mH是一個Handler的子類,所以很明顯,activityresult在這個環節是通過handler傳遞的。
在H的handleMessage方法中有這樣一個case分支
case SEND_RESULT:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
handleSendResult((ResultData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
複製程式碼
不用再多說了吧,就是這裡了。它呼叫了handleSendResult((ResultData)msg.obj),所以這個ResultData應該就是存放了我們需要的東西。只要能攔截到它,拿出我們需要的資料,那就算是hook到了onActivityResult了,那麼怎麼拿到呢?現在就要提到我們剛才提的Handler.Callback了,我們建立一個callback,並記得讓handleMessage的方法返回false,以免影響mH本身的handleMessage方法的執行,然後通過反射把這個callback給mH set進去。下面需要您對反射有一點點了解。
public class HookUtil {
public static void hookActivityThreadHandler() throws Exception {
// 先獲取到當前的ActivityThread物件
final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
final Object currentActivityThread = currentActivityThreadField.get(null);
// 由於ActivityThread一個程式只有一個,我們獲取這個物件的mH
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(currentActivityThread);
Handler.Callback mHCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if(msg.what == 108){
Log.d("hook-------","onActivityResult");
try{
Object resultData = msg.obj;
Field mActivitiesField = activityThreadClass.getDeclaredField("mActivities");
mActivitiesField.setAccessible(true);
ArrayMap mActivities = (ArrayMap) mActivitiesField.get(currentActivityThread);
Class<?> resultDataClass = Class.forName("android.app.ActivityThread$ResultData");
Field tokenField = resultDataClass.getDeclaredField("token");
tokenField.setAccessible(true);
IBinder token = (IBinder) tokenField.get(resultData);
//r是ActivityClientRecord型別的
Object r = mActivities.get(token);
Class<?> ActivityClientRecordClass = Class.forName("android.app.ActivityThread$ActivityClientRecord");
Field activityField = ActivityClientRecordClass.getDeclaredField("activity");
activityField.setAccessible(true);
Activity activity = (Activity) activityField.get(r); //至此,終於拿到activity了
Field resultsField = resultDataClass.getDeclaredField("results");
resultsField.setAccessible(true);
List results = (List) resultsField.get(resultData);
//ResultInfo型別
Object resultInfo = results.get(0);
Class<?> resultInfoClass = Class.forName("android.app.ResultInfo");
Field mRequestCodeField = resultInfoClass.getDeclaredField("mRequestCode");
mRequestCodeField.setAccessible(true);
int mRequestCode = (int) mRequestCodeField.get(resultInfo); //拿到requestCode
Field mResultCodeField = resultInfoClass.getDeclaredField("mResultCode");
mResultCodeField.setAccessible(true);
int mResultCode = (int) mResultCodeField.get(resultInfo); //拿到resultCode
Field mDataField = resultInfoClass.getDeclaredField("mData");
mDataField.setAccessible(true);
Intent mData = (Intent) mDataField.get(resultInfo); //拿到intent
new OnResultManager(activity).trigger(mRequestCode,mResultCode,mData);
}catch (Exception e){
e.printStackTrace();
}
}
return false;
}
};
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mH, mHCallback);
}
}
複製程式碼
hookActivityThreadHandler方法中就是先獲取到ActivityThread物件,再通過activityThread獲取到mH,然後建立了一個Handler.Callback,最後用反射把這個callback set給mH。
相比之下,Handler.Callback中的handleMessage方法看起來更長一些,但其實並不複雜,只是反射使它變得面目全非了,它主要就是為了獲取我們OnResultManager的trigger方法需要的資料,一個activity,以及requestCode、resultCode、data。
msg.what==108的判斷,這裡108就是SEND_RESULT,我圖方便直接寫死了,原始碼裡看著為108,當然通過反射動態獲取SEND_RESULT更規範一點,大家別在意這些細節。
先看activity怎麼獲取,主要看ActivityThread的handleSendResult方法,它的第一行
private void handleSendResult(ResultData res) {
ActivityClientRecord r = mActivities.get(res.token);
複製程式碼
這個res就是msg.obj,而獲取到ActivityClientRecord之後,它有一個名為activity的field,獲取的就是我們需要的activity物件了。本身很簡單,只不過這短短的一行程式碼要用反射一點一點獲取,就繁瑣一些了。
另外三個資料都在一起,在ResultData裡有個名為results的Field,它是一個List,裡面就一個元素(可能有誤,但我打斷點看它一直都是一個),是ResultInfo型別,我們需要的requestCode、resultCode、data就都在這個resultInfo裡面了,所以,繼續反射。
都獲取完了,呼叫
new OnResultManager(activity).trigger(mRequestCode,mResultCode,mData);
複製程式碼
就是把我們之前手動寫在onActivityResult中的那句放到這裡自動觸發。
現在方法寫完了,還要寫個自定義Application,在onCreate中呼叫
HookUtil.hookActivityThreadHandler()
複製程式碼
大功告成啦!,趕緊測試一下吧,現在終於不用再在onActivityResult中手動觸發啦!github上程式碼已經更新。
按理說我們該在OnResultManager中定義個init方法,然後在init方法中去調hookActivityThreadHandler貌似更規範一些,不過這隻算個demo,就不考慮這麼多了。
還有,這種通過反射hook的方法在穩定性和相容性上都無法保證,所以這算是一種僅供娛樂的方式吧!領略一下hook的魅力。
下面是world_hello實現的hook,雖然思路是一樣的,但是編寫的要比我的容易看懂得多,自愧不如,在此強烈推薦大家看一下。
第二種方式(參考RxPermissions的做法)
前段時間又來了個小專案,領導扔給我了,然後在這個專案裡就把之前沒用過(沒錯,之前總用H5開發……)的rxjava、kotlin都加進來了。有一天做動態許可權處理,驚奇地發現RxPermissions居然擺脫了Activity的onRequestPermissionsResult方法!!!大家都知道,動態許可權大體就是先呼叫requestPermissions方法,然後授權的結果要到onRequestPermissionsResult中處理,簡直和startActivityForResult如出一轍。那RxPermissions是怎麼做到的呢!!!
接著在前幾天不太忙的時候看了下RxPermissions的原始碼,發現它內部持有一個Fragment,這個fragment沒有檢視,只負責請求許可權和返回結果,相當於一個橋樑的作用,我們通過rxPermissions發起request的時候,其實並不是activity去request,而是通過這個fragment去請求,然後在fragment的onRequestPermissionsResult中把結果傳送出來,如此來避開activity的onRequestPermissionsResult方法。
當時,沒見過什麼大場面的我差點就給跪了。
RxPermissions的原始碼就不貼了,網上的講解應該也很多。同樣,Fragment也有startActivityForResult方法啊,那我們也可以採取類似的方法,為所欲為。
這次取名叫AvoidOnResult,主要就是AvoidOnResult和AvoidOnResultFragment兩個類。先上程式碼:
public class AvoidOnResult {
private static final String TAG = "AvoidOnResult";
private AvoidOnResultFragment mAvoidOnResultFragment;
public AvoidOnResult(Activity activity) {
mAvoidOnResultFragment = getAvoidOnResultFragment(activity);
}
public AvoidOnResult(Fragment fragment){
this(fragment.getActivity());
}
private AvoidOnResultFragment getAvoidOnResultFragment(Activity activity) {
AvoidOnResultFragment avoidOnResultFragment = findAvoidOnResultFragment(activity);
if (avoidOnResultFragment == null) {
avoidOnResultFragment = new AvoidOnResultFragment();
FragmentManager fragmentManager = activity.getFragmentManager();
fragmentManager
.beginTransaction()
.add(avoidOnResultFragment, TAG)
.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
}
return avoidOnResultFragment;
}
private AvoidOnResultFragment findAvoidOnResultFragment(Activity activity) {
return (AvoidOnResultFragment) activity.getFragmentManager().findFragmentByTag(TAG);
}
public Observable<ActivityResultInfo> startForResult(Intent intent, int requestCode) {
return mAvoidOnResultFragment.startForResult(intent, requestCode);
}
public Observable<ActivityResultInfo> startForResult(Class<?> clazz, int requestCode) {
Intent intent = new Intent(mAvoidOnResultFragment.getActivity(), clazz);
return startForResult(intent, requestCode);
}
public void startForResult(Intent intent, int requestCode, Callback callback) {
mAvoidOnResultFragment.startForResult(intent, requestCode, callback);
}
public void startForResult(Class<?> clazz, int requestCode, Callback callback) {
Intent intent = new Intent(mAvoidOnResultFragment.getActivity(), clazz);
startForResult(intent, requestCode, callback);
}
public interface Callback {
void onActivityResult(int requestCode, int resultCode, Intent data);
}
}
複製程式碼
public class AvoidOnResultFragment extends Fragment {
private Map<Integer, PublishSubject<ActivityResultInfo>> mSubjects = new HashMap<>();
private Map<Integer, AvoidOnResult.Callback> mCallbacks = new HashMap<>();
public AvoidOnResultFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public Observable<ActivityResultInfo> startForResult(final Intent intent, final int requestCode) {
PublishSubject<ActivityResultInfo> subject = PublishSubject.create();
mSubjects.put(requestCode, subject);
return subject.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
startActivityForResult(intent, requestCode);
}
});
}
public void startForResult(Intent intent, int requestCode, AvoidOnResult.Callback callback) {
mCallbacks.put(requestCode, callback);
startActivityForResult(intent, requestCode);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//rxjava方式的處理
PublishSubject<ActivityResultInfo> subject = mSubjects.remove(requestCode);
if (subject != null) {
subject.onNext(new ActivityResultInfo(requestCode, resultCode, data));
subject.onComplete();
}
//callback方式的處理
AvoidOnResult.Callback callback = mCallbacks.remove(requestCode);
if (callback != null) {
callback.onActivityResult(requestCode, resultCode, data);
}
}
}
複製程式碼
AvoidOnResult
先看AvoidOnResult,和RxPermissions一樣,也持有一個無檢視的fragment,在構造器中先去獲取這個AvoidOnResultFragment,getAvoidOnResultFragment、findAvoidOnResultFragment這兩個方法是從RxPermissions扒來的,大體就是先通過TAG獲取fragment,如果是null就新建一個並add進去。
這個類內部也定義了一個Callback介面,不必多說。
繼續,這個類有多個startForResult方法,主要看public void startForResult(Intent intent, int requestCode, Callback callback),它本身不幹實事,只是呼叫fragment的同名方法,所有的邏輯都是fragment中處理,待會兒我們來看這個“同名方法”。
AvoidOnResultFragment
再看這個fragment,它持有一個mCallbacks,存著requestCode和callback的對應關係。然後找到上邊說的同名方法startForResult,只有兩行程式碼,1、把callback存起來,2、呼叫fragment的startActivityForResult
繼續看fragment的onActivityResult方法,主要看註釋有“callback方式的處理”的程式碼,就是從mCallbacks中拿到requestCode對應的callback,呼叫callback的onActivityResult方法。總體的就是這樣了,是不是很簡單。
擴充
可以看到除了返回值為void的startForResult方法外,還有幾個返回值為Observable的,原理一樣,只不過fragment中不再是存callback,而是存subject,然後通過doOnSubscribe使它在subscribe的時候跳轉頁面,最後把得到的Observable返回。對應的,在onActivityResult中拿到對應的subject,通過onNext把資料發出去。
使用示例:
//callback方式
callback.setOnClickListener {
AvoidOnResult(this).startForResult(FetchDataActivity::class.java, REQUEST_CODE_CALLBACK, object : AvoidOnResult.Callback {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) =
if (resultCode == Activity.RESULT_OK) {
val text = data?.getStringExtra("text")
Toast.makeText(this@MainActivity, "callback -> " + text, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this@MainActivity, "callback canceled", Toast.LENGTH_SHORT).show()
}
})
}
//rxjava方式
rxjava.setOnClickListener {
AvoidOnResult(this)
.startForResult(FetchDataActivity::class.java, REQUEST_CODE_RXJAVA)
//下面可自由變換
.filter { it.resultCode == Activity.RESULT_OK }
.flatMap {
val text = it.data.getStringExtra("text")
Observable.fromIterable(text.asIterable())
}
.subscribe({
Log.d("-------> ", it.toString())
}, {
Toast.makeText(this, "error", Toast.LENGTH_SHORT).show()
}, {
Toast.makeText(this, "complete", Toast.LENGTH_SHORT).show()
})
}
複製程式碼
所有的工具類都是用java寫的,避免使用kotlin編寫,出現java無法呼叫kotlin的情況。測試程式碼用的kotlin,不過沒用太多kotlin的特性,所以即便沒接觸過kotlin的應該也很好看懂吧!
最後祝大家新年快樂!要是能賞幾個star就更好啦!