Glow Android 优化实践

了解 Glow 的朋友应该知道,我们主营四款 App,分别是 Eve、Glow、Nuture和Baby。作为创业公司,我们的四款 App 都处于高速开发中,平均每个 Android App 由两人负责开发,同时负责 Android 和 Server 开发,在满足 PM 各种需求的同时,我们的 session crash free 率保持不低于 99.8%,其中两款 App 接近 100%。

本文将对 Glow 当前 Android App 中对现有工具的探索及优化进行讲解,希望对读者有所启发。

整体结构概览

下面是 Glow Android 端的大体结构:

我们有四个 Android App,它们共用同一个 Community 社区,最底层是 Base-Library,存放公用的模块组件,如支付模块,Logging模块等等。

下面,我将依次从以下几个方面进行讲解:

  • 网络层优化
  • 内存优化实践
  • 在 App 和 Library 中集成依赖注入
  • etc.

网络层优化

1. Retrofit2 + OkHttp3 + RxJava

上面这套结构是目前最为流行的网络层架构,可以帮我们写出简洁而稳定的网络请求代码,比起以前复杂的异步回调、主次线程切换等代码更为易用,而且能支持 https 请求。

基本用法如下:

  • UserApi userApi = retrofit.create(UserApi.class);
  • @Get("/{id}")
  • Observable<User> getUser(@Path("id") long id);
  • userApi.getUser(1)
  • .subscribeOn(Schedulers.io())
  • .observeOn(AndroidSchedulers.mainThread())
  • .subscribe(new Action1<User>() {
  • @Override
  • public void call(User user) {
  • // handle user
  • }
  • }, new Action1<Throwable>() {
  • @Override
  • public void call(Throwable throwable) {
  • // handle throwable
  • }
  • });

这只是通用做法。下面我们要根据实际情况进行优化。

2. 封装线程切换代码

上面的代码中可以看到,为了执行网络请求,我们会利用RxJava提供的Schedulers工具来方便切换线程。

  • .subscribeOn(Schedulers.io())
  • .observeOn(AndroidSchedulers.mainThread())

上面的代码的作用是:让网络请求进入 io线程 执行,并将返回结果转入 UI线程 去进行渲染。

不过,我们 app 有非常多的网络请求,而且除了网络请求,其他的数据库操作 或者 文件读写操作 都需要一样的线程切换。因此,为了代码复用,我们利用 RxJava 提供的 Transformer 来进行封装。

  • // RxUtil.java
  • public static <T> Observable.Transformer<T, T> normalSchedulers() {
  • return new Observable.Transformer<T, T>() {
  • @Override
  • public Observable<T> call(Observable<T> source) {
  • return source.subscribeOn(Schedulers.io())
  • .observeOn(AndroidSchedulers.mainThread());
  • }
  • };
  • }

然后,我们可以把网络请求代码转化为

  • userApi.getUser(1)
  • .compose(RxUtil.normalSchedulers())
  • .subscribe(...)

这虽然只是很简单的改进,但能让我们的代码更简洁,更不易出错。

3. 封装响应结果 JsonDataResponse

我们 server 的所有返回结果都符合如下格式:

  • {
  • 'rc': 0,
  • 'data': {...},
  • 'msg': "Successful Call"
  • }

其中 rc 是自定义的结果标志,server 用来告诉我们该请求的逻辑处理是否成功(此时 rc = 0)。data是这个请求需要的 json 数据。msg一般用来存放错误提示信息。

于是我们创建了一个通用类来封装所有的 Response

  • public class JsonDataResponse<T> {
  • @SerializedName("rc")
  • private int rc;
  • @SerializedName("msg")
  • private String msg;
  • @SerializedName("data")
  • T data;
  • public int getRc() { return rc; }
  • public T getData() { return data; }
  • }

于是,我们的请求变成如下:

  • @Get("/{id}")
  • Observable<JsonDataResponse<User>> getUser(@Path("id") long id);
  • userApi.getUser(1)
  • .compose(RxUtil.normalSchedulers())
  • .subscribe(new Action1<JsonDataResponse<User>>() {
  • @Override
  • public void call(JsonDataResponse<User> response) {
  • if (response.getRc() == 0) {
  • User user = response.getData();
  • // handle user
  • } else {
  • Toast.makeToast(context, response.getMsg())
  • }
  • }
  • }, new Action1<Throwable>() {
  • @Override
  • public void call(Throwable throwable) {
  • // handle throwable
  • }
  • });

4. 异常处理

上面已经能完成正常的网络请求了,但是,却还没有对错误进行处理。

一次网络请求中,可能发生以下几种错误:

  • 没有网络
  • 网络正常,但 http 请求失败,即 http 状态码不在 [200, 300) 之间,如404500
  • 网络正常,http 请求成功,但是 server 在处理请求时出了问题,使得返回结果的 rc != 0

不同的错误,我们希望给用户不同的提示,并且统计这些错误。

目前我们的网络请求里已经能够处理第三种情况,另外两种都在 throwable 里面,我们可以通过判断 throwableIOException 还是 retrofit2.HttpException 来区分这两种情况。

因此,我们可得到如下异常处理代码:

  • userApi.getUser(1)
  • .compose(RxUtil.normalSchedulers())
  • .subscribe(new Action1<JsonDataResponse<User>>() {
  • @Override
  • public void call(JsonDataResponse<User> response) {
  • if (response.getRc() == 0) {
  • User user = response.getData();
  • // handle user
  • handleUser();
  • } else {
  • // such as: customized errorMsg: "cannot find this user".
  • Toast.makeToast(context, response.getMsg(), Toast.LENGTH_SHORT).show();
  • }
  • }
  • }, new Action1<Throwable>() {
  • @Override
  • public void call(Throwable throwable) {
  • String errorMsg = "";
  • if (throwable instanceof IOException) {
  • // io Exception
  • errorMsg = "Please check your network status";
  • } else if (throwable instanceof HttpException) {
  • HttpException httpException = (HttpException) throwable;
  • // http error.
  • errorMsg = httpException.response();
  • } else {
  • errorMsg = "unknown error";
  • }
  • Toast.makeToast(...);
  • }
  • });

5. 封装异常处理代码

当然,我们并不想在每一个网络请求里都写上面一大段代码来处理 error,那样太傻了。比如上面 getUser() 请求,我希望只要写 handleUser() 这个方法,至于是网络问题还是 server 自己问题我都不想每次去 handle。

接下来我们来封装上面两个 Action 。我们可以自定义两个 Action:

  • WebSuccessAction<T extends JsonDataResponse> implements Action1<T>
  • WebFailureAction implements Action1<Throwable>

其中,WebSuccessAction 用来处理一切正常(网络正常,请求正常,rc=0)后的处理,WebFailureAction 用来统一处理上面三种 error

实现如下:

  • class WebSuccessAction<T extends JsonDataResponse> implements Action1<T> {
  • @Override
  • public void call(T response) {
  • int rc = response.getRc();
  • if (rc != 0) {
  • throw new ResponseCodeError(extendedResponse.getMessage());
  • }
  • onSuccess(extendedResponse);
  • }
  • public abstract void onSuccess(T extendedResponse);
  • }
  • // (rc != 0) Error
  • class ResponseCodeError extends RuntimeException {
  • public ResponseCodeError(String detailMessage) {
  • super(detailMessage);
  • }
  • }

WebSuccessAction 里,我们把 rc != 0 这种情况转化成 ResponseCodeError 并抛出给 WebFailureAction 去统一处理。

  • class WebFailAction implements Action1<Throwable> {
  • @Override
  • public void call(Throwable throwable) {
  • String errorMsg = "";
  • if (throwable instanceof IOException) {
  • errorMsg = "Please check your network status";
  • } else if (throwable instanceof HttpException) {
  • HttpException httpException = (HttpException) throwable;
  • // such as: "server internal error".
  • errorMsg = httpException.response();
  • } else {
  • errorMsg = "unknown error";
  • }
  • Toast.makeToast(...);
  • }
  • }

有了上面两个自定义 Action 后,我们就可以把前面 getUser() 请求转化如下:

  • userApi.getUser(1)
  • .compose(RxUtil.normalSchedulers())
  • .subscribe(new WebSuccessAction<JsonDataResponse<User>>() {
  • @Override
  • public void onSuccess(JsonDataResponse<User> response) {
  • handleUser(response.getUser());
  • }
  • }, new WebFailAction())

Bingo! 至此我们能够用非常简洁的方式来执行网络操作,而且完全不用担心异常处理。

内存优化实践

在内存优化方面,Google 官方文档里能找到非常多的学习资料,例如常见的内存泄漏、bitmap官方最佳实践。而且 Android studio 里也集成了很多有效的工具如 Heap Viewer, Memory MonitorHierarchy Viewer 等等。

下面,本文将从其它角度出发,来对内存作进一步优化。

1. 当Activity关闭时,立即取消掉网络请求结果处理。

这一点很容易被忽略掉。大家最常用的做法是在 Activity 执行网络操作,当 Http Response 回来后直接进行UI渲染,却并不会去判断此时 Activity 是否仍然存在,即用户是否已经离开了当时的页面。

那么,有什么方法能够让每个网络请求都自动监听 Activity(Fragment) 的 lifecycle 事件并且当特定 lifecycle 事件发生时,自动中断掉网络请求的继续执行呢?

首先来看下我们的网络请求代码:

  • userApi.getUser(1)
  • .compose(RxUtil.normalSchedulers())
  • .subscribe(new WebSuccessAction<JsonDataResponse<User>>() {
  • @Override
  • public void onSuccess(JsonDataResponse<User> response) {
  • handleUser(response.getUser());
  • }
  • }, new WebFailAction())

我们希望达到的是,当 Activity 进入 onStop 时立即停掉网络请求的后续处理。

这里我们参考了 RxLifecycle 的实现方式,之所以没有直接使用 RxLifecycle 是因为它必须我们的 BaseActivity 继承其提供的 RxActivity ,而 RxActivity 并未继承我们需要的 AppCompatActivity。因此本人只能在学习其源码后,自己重新实现一套,并做了一些改动以更符合我们自己的应用场景。

具体实现如下:

  • 首先,我们在 BaseActivity 里,利用 RxJava 提供的 PublishSubject 把所有 lifecycle event 发送出来。
    ```java
    class BaseActivity extends AppCompatActivity {
    protected final PublishSubject lifecycleSubject = PublishSubject.create();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    lifecycleSubject.onNext(ActivityLifeCycleEvent.CREATE);

top Created with Sketch.