Android 一行代码完成startActivityForResult+回调处理+协程 InlineActivityResult 使用方法和原理解析
前言
最近项目逐渐转换到Kotlin+Jetpack+协程的开发模式,这种顺序事件写法深得我心,实在是太方便了,再也不用嵌套无数个回调,或者一个任务却写了跳来跳去实现代码,回头看的时候实在太恶心人了,到处写注释,真的怕自己都能忘了。
但是,startActivityForResult这个API还是得跳到onActivityResult来继续写,实在郁闷。好比如当我们有个业务需求是:
请求文件读取写入权限;
打开系统相册,选取一张图片后返回;
打开系统图片裁剪工具,按照规定格式裁剪后返回;
将裁剪后的图片上传
像这种需求,跳转到其他页面再回来的,一个工作还要跳几次的,代码连续性的逻辑就得注释写好多了。
InlineActivityResult
恶心了自己几天后,还是去找了一下有没有解决方法,让我能像写协程代码一样,在一个方法函数里写完一个任务的逻辑;还真的被我找到了一个库,实在太帅了,还有230个star,马上试用了一下,真香。
florent37/InlineActivityResult
使用方法
GitHub上的README已经写得很清楚了,不过要注意的是,一定要用try catch来包住这个跳转请求
,不然容易发生意想不到的崩溃。
我的使用例子:
/** 打开相册 */
private fun openAlbum() {
val intent = Intent(Intent.ACTION_PICK, null)
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
GlobalScope.launch {
try {
val result = startForResult(intent)
if (result.resultCode == Activity.RESULT_OK) openRawCrop(
result.data?.data ?: return@launch
)
} catch (e: Exception) {}
}
}
混淆规则:
# 协程startActivityForResult
-dontwarn com.github.florent37.inlineactivityresult.kotlin.**
-keep class com.github.florent37.inlineactivityresult.kotlin.** {*;}
-dontwarn com.github.florent37.inlineactivityresult.**
-keep class com.github.florent37.inlineactivityresult.** {*;}
原理解析
终于来到重头戏了,用完这个库后,我一直非常好奇到底是如何实现的,而且这么有趣的东西,不明白原理的话,会一直让我知其然不知其所以然。
所以,先下载工程打开。
很明显,MainActivityJava7
就是最基础的一个入口;
public class MainActivityJava7 extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
request.setOnClickListener(view -> myMethod(RequestFabric.create(new Intent(MediaStore.ACTION_IMAGE_CAPTURE))));
}
private void myMethod(Request request) {
new InlineActivityResult(this)
.startForResult(request, new ActivityResultListener() {
@Override
public void onSuccess(Result result) {
Bundle extras = result.getData().getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
resultView.setImageBitmap(imageBitmap);
}
@Override
public void onFailed(Result result) {
}
});
}
}
代码可以说是非常的简洁,点击request
这个控件,调用myMethod
方法,而myMethod
里只有一个参数Request
,而这个Request
是由RequestFabric
这个类创建的,我们继续往这个类走
public class RequestFabric {
...
/**
* Create request for call {@link android.app.Activity#startActivityForResult(Intent, int, Bundle)}.
*/
public static Request create(Intent intent) {
return new RequestActivityForResult(intent, null);
}
}
可以看到,直接就返回了一个RequestActivityForResult
实例,那就来仔细瞧瞧这个RequestActivityForResult
到底藏了什么葫芦药
public class RequestActivityForResult implements Request {
private final Intent intent;
@Nullable
private Bundle options;
...
public RequestActivityForResult(Intent intent, @Nullable Bundle options) {
this.intent = intent;
this.options = options;
}
...
@Override
public void execute(@NonNull Fragment fragment, int requestCode) {
fragment.startActivityForResult(intent, requestCode, options);
}
...
}
emmmm,RequestActivityForResult
其实就是一个结构体,就是来存储Intent
和传递参数Bundle
的,只不过继承了Request
,多个了execute
的函数,但是execute
还没看到被调用,先不管他。
回到MainActivityJava7
,上面创建好的Request
就被传递进了myMethod
这个函数。这个函数也相当简洁,直接就是建立一个InlineActivityResult
实例,然后直接调用它的startForResult
方法,并带进去回调参数,回调参数就不用管啦
new InlineActivityResult(this)
.startForResult(request, new ActivityResultListener() {
@Override
public void onSuccess(Result result) {
// 成功
}
@Override
public void onFailed(Result result) {
// 失败
}
});
先来看看InlineActivityResult
又是个什么东西,点进去,一目了然,存储了activity
实例的弱引用
public InlineActivityResult(@Nullable final FragmentActivity activity) {
if (activity != null) {
this.activityReference = new WeakReference<>(activity);
} else {
this.activityReference = new WeakReference<>(null);
}
this.fragmentReference = new WeakReference<>(null);
}
再看startForResult
方法,可以看到把回调监听器给存了起来,并且使用了一个start
函数
public InlineActivityResult startForResult(@Nullable final Request request, @Nullable final ActivityResultListener listener) {
if (request != null) {
if (listener != null) {
this.responseListeners.add(listener);
}
this.start(request);
}
return this;
}
继续往下看start
函数,好家伙,新建了一个ActivityResultFragment
实例,并且在activity
中加入了这个fragment
,相当于activity的Fragment栈中最顶的从当前页面变成了这个新建的ActivityResultFragment
实例。然后,就没了
private void start(@NonNull final Request request) {
final FragmentActivity activity = activityReference.get();
if (activity == null || activity.isFinishing()) {
listener.error(new ActivityNotFoundException("activity is null or finished"));
return;
}
final ActivityResultFragment newFragment = ActivityResultFragment.newInstance(request, listener);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
FragmentManager fragmentManager;
Fragment fragment = fragmentReference.get();
if (fragment != null) {
fragmentManager = fragment.getChildFragmentManager();
} else {
fragmentManager = activity.getSupportFragmentManager();
}
fragmentManager.beginTransaction()
.add(newFragment, TAG)
.commitNowAllowingStateLoss();
}
});
}
???这就没了,逗我呢,怎么回调的呀
不着急,ActivityResultFragment
做了啥工作,还需要仔细看看
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class ActivityResultFragment extends Fragment {
...
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//Execute Request
executeRequest();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (listener != null) {
listener.onActivityResult(requestCode, resultCode, data);
}
removeFragment();
}
}
public void executeRequest() {
if (request != null) {
try {
request.execute(this, REQUEST_CODE);
} catch (Exception e) {
e.printStackTrace();
if (listener != null) {
listener.error(e);
}
removeFragment();
}
} else {
// this shouldn't happen, but just to be sure
if (listener != null) {
listener.error(new NullPointerException("request is empty"));
}
removeFragment();
}
}
}
嘿嘿,找到了,在fragment启动的时候执行了executeRequest
函数,而这个函数只有一句是业务逻辑,就是request.execute(this, REQUEST_CODE);
啊这…
是不是好像哪里见过这个方法?
就是前面RequestActivityForResult
这个类里面的方法,只做了一件事fragment.startActivityForResult(intent, requestCode, options);
终于跳转页面了。
最后在onActivityResult
里做监听并且通过回调函数回调回去就完事了
舒爽
总结
这个库的实现方式相当巧妙,起码我没有想到这种方法
先在activity里增加一个特定的Fragment,而这个Fragment只做一个工作,就是根据传递进来的参数,进行页面跳转,并在onActivityResult函数中将返回内容通过回调传回去给调用者
完事
感谢florent37提供这个优秀的开源库