如何在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协程将异步逻辑同步化,注意异常处理和取消处理