热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

告别onActivityResult

一、背景和目标我们先来看下正常情况下启动Activity和接收回调信息的方式:@OverrideprotectedvoidonCreate(BundlesavedInstanceS

一、背景和目标

我们先来看下正常情况下启动Activity和接收回调信息的方式:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 启动Activity
startActivityForResult(new Intent(this, TestActivity.class), 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 接收Activity回调
if (requestCode == 1) {
// 处理回调信息
}
}

这样看起来似乎也简洁,但是会有两个问题:

  • onActivityResult必须在原始Activity中才能接收,如果想在非Activity中调用startActivityForResult,那么调用和接收的地方就不在同一个地方了,代码可读性会大大降低。
  • onActivityResult中所有的页面跳转回调处理都会在这里,需要通过对resultCode进行if...else...判断才能区分是哪个跳转的回调,如果跳转比较多的话,看起来会特别烦人。

那么我们希望的是,可不可以像按钮点击事件一样通过回调的方式接收页面跳转的回调信息呢?在哪调用的跳转,就在哪接收回调,这样看起来就会爽多了,提高代码可读性,也有利于模块的封装和隔离。类似于下面这样:

// 启动Activity
startActivityForResult(TestActivity.class, new Callback() {
@Override
public void onActivityResult(int resultCode, Intent data) {
// 处理回调信息
}
});

这样看起来是不是更加简洁呢~~~

二、探索与实现

1. 先介绍一种比较直接容易想到的方法:
通过一个代理类ActivityLauncher,封装一下startActivityForResultonActivityResult方法。

public class ActivityLauncher {
private FragmentActivity mActivity;
private SparseArray mCallbacks = new SparseArray<>();
private ActivityLauncher(FragmentActivity activity) {
mActivity = activity;
}
public void startActivityForResult(Intent intent, int requestCode, Callback callback) {
// 保存下Callback
mCallbacks.put(requestCode, callback);
mActivity.startActivityForResult(intent, requestCode);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// 取出对应Callback
Callback callback = mCallbacks.get(resultCode);
if (callback != null) {
callback.onActivityResult(requestCode, resultCode, data);
}
}
public interface Callback {
void onActivityResult(int requestCode, int resultCode, Intent data);
}
}

如何使用:

private ActivityLauncher mActivityLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 启动Activity
Intent intent = new Intent(this, TestActivity.class);
mActivityLauncher = new ActivityLauncher(this);
mActivityLauncher.startActivityForResult(intent, 1, new ActivityLauncher.Callback() {
@Override
public void onActivityResult(int resultCode, Intent data) {
// 接收Activity回调
if (requestCode == 1) {
// 处理回调信息
}
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mActivityLauncher.onActivityResult(requestCode, resultCode, data);
}

这种方式看似简单,但是实际用起来会有点问题。这里的onActivityResult仍然需要外部ActivityonActivityResult来手动调用,显得很麻烦,调用者很容易忘记,而且这样也没有做到真正的跟Activity隔离,其实本质上跟原始的方式没太大区别。

其实这种方式,只解决了上面提到的第二个问题,把onActivityResult中的很多不同跳转的回调逻辑分散到了各自调用跳转的地方,看起来会清爽一些。但是第一个问题还是没解决,我们需要把调用跳转的地方和接收回调的地方真正的绑定在一起,不需要其他多余的耦合。

2. 利用java反射和hook技术
既然需要跟外部ActivityonActivityResult解耦,我们能想到的就是,如果上面的那种方法,可以自动帮我们调用onActivityResult那该多好。我们能想到的一种方法就是利用java的反射机制,把ActivityonActivityResult调用流程反射出来,并且注入我们自己改造后的代码,替换原来的流程,实现自动调动onActivityResult的目的。
这里贴一段网上找到的实现hook的方案,有兴趣的同学可以拿来研究下:

public class ActivityThreadCallbackHook {
// Copy from ActivityThread.mH Handler
public static final int SEND_RESULT = 108;
public static void hook() {
try {
ActivityThread activityThread = ActivityThread.currentActivityThread();
// 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
Field mHField;
mHField = ActivityThread.class.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(activityThread);
// 设置它的回调, 根据源码:
// 我们自己给他设置一个回调,就会替代之前的回调;
// public void dispatchMessage(Message msg) {
// if (msg.callback != null) {
// handleCallback(msg);
// } else {
// if (mCallback != null) {
// if (mCallback.handleMessage(msg)) {
// return;
// }
// }
// handleMessage(msg);
// }
// }
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
// 塞入我们的 hook 对象
mCallBackField.set(mH, new MyHandlerCallback(mH));
Log.d("hook","success");
} catch (Exception e) {
// hook 失败,整个 callback 就 gg了
e.printStackTrace();
}
}
private static class MyHandlerCallback implements Handler.Callback {
private Handler mOldHandler;
public MyHandlerCallback(Handler mOldHandler) {
this.mOldHandler = mOldHandler;
}
@Override
public boolean handleMessage(Message msg) {
// 不干扰系统分发逻辑
mOldHandler.handleMessage(msg);
// 通知 ResultManager
if (msg.what == SEND_RESULT) {
Object obj = msg.obj;
try {
// step 1 reflect to get activity
Object token = ReflectUtils.on(obj).get("token");
ArrayMap mActivities = (ArrayMap) ReflectUtils.on(ActivityThread.currentActivityThread()).get("mActivities");
Object activityClientRecord = mActivities.get(token);
Activity activity = (Activity) ReflectUtils.on(activityClientRecord).get("activity");
// step2 reflect to get ResultInfo
// 注意这里的分发,无法分发到 Fragment 内部,所以采用动态塞入一个 Fragment 是最稳定的方案
ArrayList results = (ArrayList) ReflectUtils.on(obj).get("results");
for (ResultInfo result : results) {
OnResultManager.getInstance().trigger(activity, result.mRequestCode, result.mResultCode, result.mData);
}
} catch (RuntimeException e) {
e.printStackTrace();
}
}
// default
return true;
}
}
}

3. 还有另一种实现注入onActivityResult的方法,就是是利用AOP技术,通过android-aspectjx插件,可以实现面向切片编程。
直接看代码:

@Aspect
public class OnResultAspect {
private static final String TAG = "OnResultAspect";
@After("execution(* android.app.Activity.onActivityResult(..))")
public void onActivityResultAfter(JoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
int requestCode = (int) args[0];
int resultCode = (int) args[1];
Intent data = (Intent) args[2];
Activity activity = (Activity) joinPoint.getTarget();
OnResultManager.getInstance().trigger(activity, requestCode, resultCode, data);
}
}

直接在每次系统调用onActivityResult的时候,插入我们自己的onActivityResult方法自动调用。

方法2方法3,基本思路是一样的,都是通过注入代码的方式实现自动调用我们自己的onActivityResult方法,但是都有不尽人意的地方:
方法2通过反射的方式,兼容性和稳定性都较差,而且容易和其他第三方插件产生冲突,出现问题也很难排查。而且,这两种方法都会对所有页面的onActivityResult都产生注入,而实际使用中可能并不需要对所有页面都生效,可控性较差。

4. 下面介绍一种既简单又稳定的方法,来实现自动调用onActivityResult
安卓系统中,Fragment有着跟Activity一样的生命周期,却比Activity更轻量级。我们可以利用一个空的无界面的Fragment来监听onActivityResult方法,并通过回调形式返回给调用者。由于FragmentonActivityResult是系统自动调用的,所以我们就可以从此简单轻松的告别onActivityResult了,而且这个都是系统自带的方法,稳定又可靠。

下面简单贴下实现代码:

public class ActivityLauncher {
private static final String TAG = "ActivityLauncher";
private Context mContext;
private RouterFragment mRouterFragment;
public static ActivityLauncher init(FragmentActivity activity) {
return new ActivityLauncher(activity);
}
private ActivityLauncher(FragmentActivity activity) {
mCOntext= activity;
mRouterFragment = getRouterFragment(activity);
}
private RouterFragment getRouterFragment(FragmentActivity activity) {
RouterFragment routerFragment = findRouterFragment(activity);
if (routerFragment == null) {
routerFragment = RouterFragment.newInstance();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
fragmentManager
.beginTransaction()
.add(routerFragment, TAG)
.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
}
return routerFragment;
}
private RouterFragment findRouterFragment(FragmentActivity activity) {
return (RouterFragment) activity.getSupportFragmentManager().findFragmentByTag(TAG);
}
public void startActivityForResult(Class clazz, Callback callback) {
Intent intent = new Intent(mContext, clazz);
startActivityForResult(intent, callback);
}
public void startActivityForResult(Intent intent, Callback callback) {
mRouterFragment.startActivityForResult(intent, callback);
}
public interface Callback {
void onActivityResult(int resultCode, Intent data);
}
}

public class RouterFragment extends Fragment {
private SparseArray mCallbacks = new SparseArray<>();
private Random mCodeGenerator = new Random();
public RouterFragment() {
// Required empty public constructor
}
public static RouterFragment newInstance() {
return new RouterFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void startActivityForResult(Intent intent, ActivityLauncher.Callback callback) {
int requestCode = makeRequestCode();
mCallbacks.put(requestCode, callback);
startActivityForResult(intent, requestCode);
}
/**
* 随机生成唯一的requestCode,最多尝试10次
*
* @return
*/
private int makeRequestCode() {
int requestCode;
int tryCount = 0;
do {
requestCode = mCodeGenerator.nextInt(0x0000FFFF);
tryCount++;
} while (mCallbacks.indexOfKey(requestCode) >= 0 && tryCount <10);
return requestCode;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
ActivityLauncher.Callback callback = mCallbacks.get(requestCode);
mCallbacks.remove(requestCode);
if (callback != null) {
callback.onActivityResult(resultCode, data);
}
}
}

如何调用:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 启动Activity
ActivityLauncher.init(this)
.startActivityForResult(TestActivity.class, new ActivityLauncher.Callback() {
@Override
public void onActivityResult(int resultCode, Intent data) {
// 处理回调信息
}
});
}

基本上实现了我们文章开头的预期。上面是我简化过后的代码,只支持FragmentActivity调用,具体可以看我的Github地址,里面实现了对FragmentFragmentActivityActivity的支持。

欢迎大家star和点赞哈~~


推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 标题: ... [详细]
  • 本文介绍了一款名为TimeSelector的Android日期时间选择器,采用了Material Design风格,可以在Android Studio中通过gradle添加依赖来使用,也可以在Eclipse中下载源码使用。文章详细介绍了TimeSelector的构造方法和参数说明,以及如何使用回调函数来处理选取时间后的操作。同时还提供了示例代码和可选的起始时间和结束时间设置。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • 在C#中,使用关键字abstract来定义抽象类和抽象方法。抽象类是一种不能被实例化的类,它只提供部分实现,但可以被其他类继承并创建实例。抽象类可以用于类、方法、属性、索引器和事件。在一个类声明中使用abstract表示该类倾向于作为其他类的基类成员被标识为抽象,或者被包含在一个抽象类中,必须由其派生类实现。本文介绍了C#中抽象类和抽象方法的基础知识,并提供了一个示例代码。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 本文介绍了Java中Currency类的getInstance()方法,该方法用于检索给定货币代码的该货币的实例。文章详细解释了方法的语法、参数、返回值和异常,并提供了一个示例程序来说明该方法的工作原理。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
author-avatar
美煤MM就
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有