如何在Android中写出优雅的异步代码?
一、面试官视角:这道题想考察什么?
- 是否熟练编写异步和同步的代码
- 是否熟练回调地狱
- 是否能够熟练使用RxJava
- 是否对kotlin协程有了解
- 是否具备编写良好的代码的意识和能力
二、题目剖析:
1、什么是异步?
1 2 3 4 5 6 7 8 9 10 11 12 13
|
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequest(req, new Callback() { @Override public void onSuccess(Response resp) { ... } }); } });
|
2、异步的目的是什么?
- 提高CPU利用率
- 提升GUI程序的响应速度
- 异步不一定快!
异步的过程中,要看程序是I/O密集型还是CPU密集型的,如果是CPU密集型的话,其实异步也好,高并发也好,往往会降低CPU的利用率,因为切换线程的时候,会有一些开销,所以对于一些GUI程序来说,特别是切线程切到I/O线程的这种异步的话,它其实是为了提高CPU的利用率,因为大多数I/O线程会被I/O阻塞掉。
3、回调地狱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequest(req, new Callback() { @Override public void onSuccess(final Response resp) { handler.post(new Runnable() { @Override public void run() { updateUI(resp); } }); } }); } });
|
4、为了解决上面的回调地狱的问题,RxJava闪亮登场!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { sendRequest(req) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Response>() { public void accept(Response response) throws Exception { updateUI(response); } }); } } });
button.setOnClickListener(v -> sendRequest(req) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(response -> updateUI(response)));
|
5、RxJava异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| button.setOnClickListener(v -> sendRequest(req) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<Response>(){ public void onSubscribe(Disponsable d) {} public void onNext(Response response) {updateUI(response);} public void onError(Throwable e) {e.printStackTrace();} public void onComplete() {} }));
button.setOnClickListener(v -> sendRequest(req) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .onErrorReturnItem(t -> mapThrowableToResponse(t)) .subscribe(response -> updateUI(response)));
RxJavaPlugins.setErrorHandler(e -> { report(e instanceof OnErrorNotImplementedException ? e.getCause() : e); Exceptions.throwIfFatal(e); });
|
6、RxJava取消处理
RxJava这个东西,执行的是异步任务,异步任务就有一个问题,那就是如果有个页面已经销毁了、退出了,这个时候就会遇到一个什么情况呢?其实我们都知道RxJava的链式调用也都是匿名内部类,只是扁平化了,那么匿名内部类它就会持有外部类的引用,比如updateUI()修改UI,就需要持有外部类的引用才能进行修改,那么这个时候Activity被持有,不可回收,但如果response -> updateUI(response),response迟迟没有返回的话,那就会造成内存泄漏,这是第一个问题,第二个问题就是,response返回了结果了,但UI已经销毁了,就有可能出现空指针问题,所以RxJava取消处理也是一个值得注意的点。
1 2 3 4 5 6
| button.setOnClickListener(v -> sendRequest(req) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .onErrorReturnItem(t -> mapThrowableToResponse(t)) .subscribe(response -> updateUI(response)));
|
如何优雅的解决上面的问题呢?(使用开源框架:AutoDispose)
1 2 3 4 5 6 7 8
| button.setOnClickListener(v -> sendRequest(req) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .onErrorReturnItem(t -> mapThrowableToResponse(t)) .as(AutoDispose.autoDisposable(ViewScopeProvider.from(buttom))) .subscribe(response -> updateUI(response)));
|
7、使用Kotlin协程来替代RxJava
kotlin的协程的好处就是,代码写起来跟写同步代码几乎一模一样。
将回调转换为协程的挂起函数:
1 2 3 4
| suspend fun sendRequest(req: Request) = suspendCoroutine<Response> { contiunation -> sendRequest(req, continuation::resume) }
|
使用挂起函数:(异步代码的同步写法)
1 2 3 4 5 6
| button.onClick { val req = Request() val resp = async {sendRequest(req)}.await() updateUI(resp) }
|
8、Kotlin协程异常处理
1 2 3 4 5 6 7 8 9 10
| button.onClick { try { val req = Request() val resp = async {sendRequest(req)}.await() updateUI(resp) }catch(e: Exception) { e.printStackTrace() } }
|
9、Kotlin协程取消处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 将onClick方法进行扩展: fun View.onClickAutoDisposable( context.CoroutineContext = Dispatchers.Main, handler: suspend CoroutineScope.(v: View?) -> Unit) { setOnClickListener{ v -> GlobalScope.launch(context, CoroutineStart.DEFAULT) { handler(v) }.asAutoDisposable(v) } }
fun Job.asAutoDisposable(view: View) = AutoDisposableJob(view, this)
class AutoDisposableJob(private val view: view, private val wrapped: Job): Job by wrapped, OnAttachStateChangeListener{ override fun onviewAttachedToWindow(v: View?) = Unit override fun onViewDetachedFromWindow(v: View?) { cancel() view.removeOnAttachStateChangeListener(this) } init { if(view.isAttachedToWindow) { view.addOnAttachStateChangeListener(this) }else { cancel() } invokeOnCompletion { view.removeOnAttachStateChangeListener(this) } } }
|
题目结论:
- 回调地狱是可怕的,可读性差,容易出问题
- 使用RxJava将异步逻辑扁平化,注意异常处理和取消处理
- 使用Kotlin协程将异步逻辑同步化,注意异常处理和取消处理