Android 一行代码完成startActivityForResult+回调处理+协程 InlineActivityResult 使用方法和原理解析

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提供这个优秀的开源库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值