Google 新提出的 App Architecture 分析

App Architecture

为什么提出新架构:

比如,当你要在自己最喜欢的社交网络app中分享一张照片的时候,你可以想象一下会发生什么。app触发一个camera intent,然后Android OS启动一个camera app来处理这一动作。此时用户已经离开了社交网络的app,但是用户的操作体验却是无缝对接的。而 camera app反过来也可能触发另一个intent,比如启动一个文件选择器,这可能会再次打开另一个app。最后用户回到社交网络app并分享照片。在这期间的任意时刻用户都可被电话打断,打完电话之后继续回来分享照片。

总的来说就是,你的app组件可能是单独启动并且是无序的,而且在任何时候都有可能被系统或者用户销毁。因为app组件生命的短暂性以及生命周期的不可控制性,任何数据都不应该把存放在app组件中,同时app组件之间也不应该相互依赖。

=> 任何数据都不应该存放在app组件中:


通用的架构准则

最重要的一个原则就是尽量在app中做到separation of concerns(关注点分离)。常见的错误就是把所有代码都写在Activity或者Fragment中。任何跟UI和系统交互无关的事情都不应该放在这些类当中。尽可能让它们保持简单轻量可以避免很多生命周期方面的问题。别忘了能并不拥有这些类,它们只是连接app和操作系统的桥梁。根据用户的操作和其它因素,比如低内存,Android OS可能在任何时候销毁它们。为了提供可靠的用户体验,最好把对它们的依赖最小化。

==> 通过MVVM来分层

第二个很重要的准则是用model驱动UI,最好是持久化的model。之所以要持久化是基于两个原因:如果OS销毁app释放资源,用户数据不会丢失;当网络很差或者断网的时候app可以继续工作。Model是负责app数据处理的组件。它们不依赖于View或者app 组件(Activity,Fragment等),因此它们不会受那些组件的生命周期的影响。保持UI代码的简单,于业务逻辑分离可以让它更易管理。

其中比较重要的一点就是为每个层级加入和绑定了生命周期这一个关系。

为了实现这个新的架构,Google提供了几种组件。

Lifecycle

  • Lifecycle 是一个持有组件(比如 activity 或者 fragment)生命周期状态信息的类,并且允许其它对象观察这个状态。

Lifecycle 主要使用两个枚举来跟踪相关组件的生命周期状态。

  • Event:
    {ON_ANY,ON_CREATE,ON_DESTROY,ON_PAUSE,ON_RESUME,ON_START,ON_STOP}

  • State:
    {CREATED,DESTROYED,INITIALIZED,RESUMED,STARTED}

  • LifecycleOwner
    需要被观察的组件(比如 activity)实现这个接口 => 被观察者

  • LifecycleObserver 需要观察其它组件生命周期的类 => 观察者

设想这样的情景:

//activity 启动的时候开始位置获取的服务
// 结束的时候暂停位置获取的服务
class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;
 
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, location -> {
            // update UI
        });    }

 
    public void onStart() {
        super.onStart();
        Util.checkUserStatus(result -> {
            //在启动的时候又需要检查一些配置(例如其它的资源准备好没有)
            //如果检查完毕后,activity.stop(),NPE。
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start();
            }
        });
    }
 
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
    }
}

如何使用 Lifecycle 解决

在 fragment / activity 这样实现了LifecycleRegistryOwner的类,则可以直接注册观察者(也可以自定义)

fragment.getLifecycle().addObserver(new MyLocationListener());

如果使用了Lifecycle,则可以让 MyLocationListener 在内部获取组件的生命周期和响应生命周期变化的回调:

class MyLocationListener implements LifecycleObserver {
  private boolean enabled = false;
  public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
     ...
  }

  @OnLifecycleEvent(Lifecycle.Event.ON_START)
  void start() {
      if (enabled) {
         // connect
      }
  }

  public void enable() {
      enabled = true;
      if (lifecycle.getState().isAtLeast(STARTED)) {
          // connect if not connected
      }
  }

  @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
  void stop() {
      // disconnect if connected
  }
}

LiveData

LiveData是一个可观察的数据持有者。 无需明确在它与app组件之间创建依赖就可以观察LiveData对象的变化。LiveData还考虑了app组件(activities, fragments, services)的生命周期状态,做了防止对象泄漏的事情。

=> 过去是一个普通的POJO类,需要手动管理,现在绑定上了生命周期以及数据变化可以被观察。
=> 有点类似带有生命周期的Rxjava。

一个普通的LiveData

  • ViewModel
public class UserProfileViewModel extends ViewModel {
    ...
    private User user;
    private LiveData<User> user;
    public LiveData<User> getUser() {
        return user;
    }
}
  • Fragment
    // Update  when the data changes.
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    viewModel.getUser().observe(this, user -> {
      // update UI
    });
}

包装后的LiveData

//现在需要一个可以获取Location数据的类
//并且需要被多处使用

public class LocationLiveData extends LiveData<Location> {
    private static LocationLiveData sInstance;
    private LocationManager locationManager;

    @MainThread
    public static LocationLiveData get(Context context) {
        if (sInstance == null) {
            sInstance = new LocationLiveData(context.getApplicationContext());
        }
        return sInstance;
    }

    private SimpleLocationListener listener = new SimpleLocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            setValue(location);
        }
    };

    private LocationLiveData(Context context) {
        locationManager = (LocationManager) context.getSystemService(
                Context.LOCATION_SERVICE);
    }

    @Override
    protected void onActive() {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
    }

    @Override
    protected void onInactive() {
        locationManager.removeUpdates(listener);
    }
}

在fragment中如下使用:

public class MyFragment extends LifecycleFragment {
    public void onActivityCreated (Bundle savedInstanceState) {
        Util.checkUserStatus(result -> {
            if (result) {
                LocationLiveData.get(getActivity()).observe(this, location -> {
                   // update UI
                });
            }
        });
  }
}

有三个方法值得注意

  • onActive():在LiveData有活跃的观察者的时候调用 => 在上面这个例子中,有观察者激活了需要获取Location数据的时候。
  • onInactive():没有活跃的观察者的时候调用 => 在这里可以暂停位置服务的更新
  • setValue(): 调用此方法通知外部观察者数据改变

这样,在多个fragment中都需要使用位置信息的时候,不仅可以共享资源,LiveData可以自行妥善管理好生命周期。

优点:

  • 没有内存泄露
  • 不会因为某个activity结束了后,还在使用activity的引用而引起崩溃
  • 因为类似屏幕旋转的操作而导致fragment重建或重新进入生命周期,fragment会立即接受到最近的数据。
  • 可以共享资源
  • 无需手动管理生命周期
  • 在fragment.onDestroy()时,就会自动移除观察者 Observer
  • 在fragment处于非活动状态时,callback不会触发。

ViewModel

一个ViewModel为特定的UI组件提供数据,比如fragment 或者 activity,并负责和数据处理的业务逻辑部分通信,比如调用其它组件加载数据或者转发用户的修改。ViewModel并不依赖于activity fragment view,也不会被configuration change影响。

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

/**
 * Application context aware {@link ViewModel}.
 * <p>
 * Subclasses must have a constructor which accepts {@link Application} as the only parameter.
 * <p>
 */
public class AndroidViewModel extends ViewModel {
    private Application mApplication;

    public AndroidViewModel(Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

在fragment中设置 UID,通过fragtment arguments传递,因为ViewModel游离在View的生命周期之外,所以当类似 configuration changed (比如屏幕旋转)的时候会自动将数据保存下来,新的fragment进入生命周期的时候,会收到相同的ViewModel。

public class UserProfileFragment extends LifecycleFragment {
    private static final String UID_KEY = "uid";
    private UserProfileViewModel viewModel;
 
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        String userId = getArguments().getString(UID_KEY);
        viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
        viewModel.init(userId);
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater,
                @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.user_profile, container, false);
    }
}
  • 避免了需要重复获取数据从而浪费资源、内存泄露的问题。

Repository

类似之前使用的Presenter层,从不同的数据源(内存、网络、硬盘)提供数据,并不需要ViewModel层关心具体操作。
另外,Google希望在这一层对于数据进行缓存和持久化。

public class UserRepository {
    private Webservice webservice;
    // ...
    public LiveData<User> getUser(int userId) {
        // This is not an optimal implementation, we'll fix it below
        final MutableLiveData<User> data = new MutableLiveData<>();
        webservice.getUser(userId).enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                // error case is left out for brevity
                data.setValue(response.body());
            }
        });
        return data;
    }
}

管理不同组件间的依赖:

现在 Google 推荐Dagger2

Room

一个ORM库

参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容