(更新)如何避免使用onActivityResult,以提高代码可读性

2024-02-20 23:10

本文主要是介绍(更新)如何避免使用onActivityResult,以提高代码可读性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

想直接看更新内容的请点此处

更新,强迫症福音,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 mActivity;

public OnResultManager(Activity activity) {
mActivity = new WeakReference(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®; //至此,终于拿到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的魅力。

OnResultManager项目地址

下面是world_hello实现的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());
ResultFragment两个类。先上代码:

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());

这篇关于(更新)如何避免使用onActivityResult,以提高代码可读性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/729825

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma