Architecture Components 是在 2017 年 Google I/O 大会上,Google 官方推出的一个构建 Android 应用架构的库。它可以帮你避免在 Android 应用开发中常见的一些问题,比如:内存泄露,管理组件生命周期等等,帮助你去构建一个健壮,易测,可维护的应用。Now available in preview目前的版本还是一个预览版,但是我们依然可以去了解一下。
Google认为在 Android 中,应用间的这种跳跃切换行为很普遍,应用程序必须把这些问题都正确的处理好。移动设备的硬件资源是很有限的,在任何时候系统都可能杀掉一些应用去释放一些资源给新的应用。那么你的应用组件的创建和销毁是不完全可控的,它可能在任何时候由于用户或者系统的行为而触发。应用组件的生命周期也不是完全由你掌控的,所以不应该存储一些数据或者状态在应用组件中,应用组件之间也不应该彼此依赖。
架构原则
- 应用中的关注点分离。不应该在一个Activity或者Fragment中写所有的代码。任何与 UI 或者交互无关的代码都不应该存在这些类中。保证他们尽可能的职责单一化将会使我们避免很多生命周期相关的问题。因为Android系统可能会随时由于用户的行为或者系统状态(比如剩余内存过低)而销毁你的应用组件,减少组件之间的依赖可以提供一个稳定的用户体验。
- 应该采用Model驱动UI的方式,最好是一个可持久化的模型。满足以下两个条件:
- 如果系统销毁我们的应用,用户不会为此而导致丢失数据。
- 在网络状况不好甚至断网的情况下,我们的应用仍然可以继续工作。
Model是专门负责为我们的应用处理和存储数据的。完全独立于View和其他应用中的组件,所以不存在生命周期相关的问题。保证UI部分的代码足够简单,没有业务逻辑,使Model管理数据的职责明确,这样更容易测试,而且稳定性更高。
框架介绍
生命周期的处理
在 android.arch.lifecycle包中提供了生命周期感知的组件的类和接口,这些组件可以根据 Activity/Fragment 的生命周期自动调整它的行为。
- LifecycleOwner:是一个具有单一方法的接口。如果一个类实现了此接口,则该类中需要持有一个Lifecycle对象,并通过 LifecycleOwner#getLifecycle()方法返回该对象。代码如下:
1 2 3 4 5
| private final LifecycleRegistry mRegistry = new LifecycleRegistry(this); @Override public LifecycleRegistry getLifecycle() { return mRegistry; }
|
如果我们需要在Activity的生命周期的某些状态中对一些工具类做对应的处理,按以前的写法:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class MyListener { public MyListener(Callback callback) { }
void start() { }
void stop() { } void enable(){ } public interface Callback{ void onLocation(long longtitude ,long latitude); } }
class MyActivity extends AppCompatActivity { private MyListener myListener;
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myListener = new MyListener(new MyListener.Callback() { @Override public void onLocation(long longtitude, long latitude) { Log.d(TAG, "onLocation: " +longtitude+","+latitude); } }); }
public void onStart() { super.onStart(); myListener.start(); }
public void onStop() { super.onStop(); myListener.stop(); } }
|
定义一个位置监听类MyLocationListener,定义它在MyActivity生命周期的onStart和onStop中需要执行的方法,start()和stop(),接着在MyActivity的对应状态中调用。
如果我们让工具类实现LifecycleObserver接口,我们需要注入MyActivity的生命周期对象Lifecycle,那么我们只需要增加注解就可以实现在对应状态调用。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class MyListener implements LifecycleObserver{ public static final String TAG="MyListener";
private boolean enabled = false; private Callback callback; private Lifecycle lifecycle; public MyListener(Lifecycle lifecycle, Callback callback) { this.lifecycle=lifecycle; this.callback =callback; }
@OnLifecycleEvent(Lifecycle.Event.ON_START) void start(){ Log.d(TAG, "start: "); }
@OnLifecycleEvent(Lifecycle.Event.ON_STOP) void stop(){ Log.d(TAG, "stop: ");
}
public void enable() { enabled = true; if(lifecycle.getCurrentState().isAtLeast(Lifecycle.State.INITIALIZED)){ getLocation(); } }
private void getLocation(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } long longtitude = (long) (Math.random()*100); long latitude = (long) (Math.random()*100); callback.onLocation(longtitude,latitude); }
public interface Callback{ void onLocation(long longtitude ,long latitude); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class MyActivity extends LifecycleActivity { private MyListener myListener; public static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myListener = new MyListener(this, getLifecycle(), new MyListener.Callback() { @Override public void onLocation(long longtitude, long latitude) { Log.d(TAG, "onLocation: " +longtitude+","+latitude); } }); getLifecycle().addObserver(myListener); myListener.enable(); } }
|
在onStart中调用就增加@OnLifecycleEvent(Lifecycle.Event.ON_START)
的注解,不用在MyActivity中调用了。
MyActivity继承了LifecycleActivity,而LifecycleActivity已经实现了LifecycleOwner的接口,这里我们需要通过getLifecycle()传入MyActivity的生命周期对象Lifecycle到工具类中即可。
工具类获得Lifecycle对象后可对MyActivity的周期状态进行判断,Lifecycle.State.INITIALIZED
这个代表生命周期所有者的初始化状态。
当然还有其他状态CREATED
DESTROYED
RESUMED
STARTED
目前这个库只是一个beta版,提供的api一直都在变动,这里就通过一个入门的小例子来学习一下。
这个小例子是展示知乎的一个资讯列表。
目前有两个可用的免费api
https://news-at.zhihu.com/api/4/news/latest
https://news-at.zhihu.com/api/4/news/before/{date}
先创建一个知乎activity,这个相当于mvp架构的v层,代码如下:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| public class ZhihuActivity extends LifecycleActivity{
private static final String TAG = "ZhihuActivity"; private SwipeRefreshLayout refreshLayout = null; private ZhihuListAdapter adapter = null; private ProgressBar loadMorebar = null; private View rootview = null;
private ZhihuListViewModel zhihuListViewModel = null;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_zhihu); initView(); subscribeUI(); }
private void initView(){ rootview=findViewById(R.id.rl_zhihu_root); loadMorebar=findViewById(R.id.bar_load_more_zhihu); refreshLayout=findViewById(R.id.refresh); RecyclerView recyclerView = findViewById(R.id.rv_zhihu_list);
LinearLayoutManager layoutManager = new LinearLayoutManager(this); adapter = new ZhihuListAdapter(this); adapter.setClickListener(new OnItemClickListener<ZhihuStory>() { @Override public void onClick(ZhihuStory zhihuStory) {
} });
recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(layoutManager); recyclerView.addOnScrollListener(new ZhihuOnScrollListener());
refreshLayout.setOnRefreshListener(new ZhihuSwipeListener()); refreshLayout.setColorSchemeResources( android.R.color.holo_blue_bright, android.R.color.holo_green_light, android.R.color.holo_orange_light, android.R.color.holo_red_light);
}
private void subscribeUI() { ZhihuListViewModel.Factory factory = new ZhihuListViewModel .Factory(getApplication() , Injection.getDataRepository(getApplication())); zhihuListViewModel = ViewModelProviders.of(this, factory).get(ZhihuListViewModel.class); zhihuListViewModel.getZhihuList().observe(this, new Observer<List<ZhihuStory>>() { @Override public void onChanged(@Nullable List<ZhihuStory> stories) { if (stories == null || stories.size() <= 0) { return; } Log.i(TAG,"size is " + stories.size()); adapter.setStoryList(stories); } }); zhihuListViewModel.isLoadingZhihuList().observe(this, new Observer<Boolean>() { @Override public void onChanged(@Nullable Boolean aBoolean) { if (aBoolean == null) { return; } Log.i(TAG,"state " + aBoolean); refreshLayout.setRefreshing(false); loadMorebar.setVisibility(aBoolean ? View.VISIBLE : View.INVISIBLE); } }); zhihuListViewModel.refreshZhihusData(); }
private class ZhihuSwipeListener implements SwipeRefreshLayout.OnRefreshListener { @Override public void onRefresh() { adapter.clearStoryList(); zhihuListViewModel.refreshZhihusData(); } }
class ZhihuOnScrollListener extends RecyclerView.OnScrollListener {
@Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int lastPosition = layoutManager .findLastCompletelyVisibleItemPosition(); if (lastPosition == adapter.getItemCount() - 1) { zhihuListViewModel.loadNextPageZhihu(lastPosition,getApplicationContext()); } } } }
|
recyclerView的adapter很简单,直接上代码:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| public class ZhihuListAdapter extends RecyclerView.Adapter<ZhihuListAdapter.ZhihuViewHolder> {
private OnItemClickListener<ZhihuStory> clickListener = null;
private Context mContext = null;
private List<ZhihuStory> mStoryList = null;
public ZhihuListAdapter(Context context) { mStoryList = new ArrayList<>(); mContext = context; }
public void setClickListener(OnItemClickListener<ZhihuStory> listener){ clickListener = listener; } @Override public ZhihuViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ZhihuViewHolder(LayoutInflater.from(parent.getContext()) .inflate(R.layout.zhihu_list_item, parent, false)); }
@Override public void onBindViewHolder(ZhihuViewHolder holder, int position) { final ZhihuStory zhihuStory = mStoryList.get(position); holder.tv_zhihu_title.setText(zhihuStory.getTitle()); holder.tv_zhihu_time.setText(zhihuStory.getGa_prefix()); holder.ll_zhihu.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(clickListener != null){ clickListener.onClick(zhihuStory); } } }); Glide.with(mContext) .load(zhihuStory.getImages()[0]) .centerCrop() .into(holder.img_zhihu); }
@Override public int getItemCount() { return mStoryList.size(); }
public void setStoryList(List<ZhihuStory> storyList) { if (storyList == null || storyList.size() == 0) { return; } mStoryList.addAll(storyList); notifyDataSetChanged(); }
public void clearStoryList() { mStoryList.clear(); notifyDataSetChanged(); }
class ZhihuViewHolder extends RecyclerView.ViewHolder {
public View ll_zhihu; public TextView tv_zhihu_title; public TextView tv_zhihu_time; public ImageView img_zhihu;
private ZhihuViewHolder(View itemView) { super(itemView); ll_zhihu = itemView.findViewById(R.id.ll_zhihu); tv_zhihu_title = itemView.findViewById(R.id.tv_zhihu_title); tv_zhihu_time = itemView.findViewById(R.id.tv_zhihu_time); img_zhihu = itemView.findViewById(R.id.img_zhihu); } } }
|
对应的item监听代码如下:
1 2 3
| public interface OnItemClickListener<T> { void onClick(T t); }
|
对应的布局文件代码如下:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ll_zhihu" android:layout_width="match_parent" android:layout_height="113dp">
<ImageView android:id="@+id/img_zhihu" android:layout_width="112dp" android:layout_height="112dp" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:layout_gravity="center_vertical" android:contentDescription="@null" android:paddingBottom="16dp" android:paddingEnd="16dp" android:paddingRight="16dp" android:paddingTop="16dp" android:src="@mipmap/ic_launcher"/>
<LinearLayout android:layout_width="match_parent" android:layout_height="112dp" android:layout_toEndOf="@+id/img_zhihu" android:layout_toRightOf="@+id/img_zhihu" android:orientation="vertical">
<TextView android:id="@+id/tv_zhihu_title" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:paddingLeft="16dp" android:paddingStart="16dp" android:paddingTop="24dp"/>
<TextView android:id="@+id/tv_zhihu_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:paddingLeft="16dp" android:paddingStart="16dp"/> </LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1px" android:layout_alignParentBottom="true" android:layout_marginLeft="112dp" android:layout_marginStart="112dp" android:background="@color/lightGrey"/> </RelativeLayout>
|
这里创建了ZhihuListViewModel,相当于mvp架构的p层。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| public class ZhihuListViewModel extends AndroidViewModel {
private MutableLiveData<String> requestPageDate = new MutableLiveData<>(); private final LiveData<List<ZhihuStory>> mZhihuList; private DataRepository dataRepository = null;
private ZhihuListViewModel(Application application, final DataRepository dataRepository) { super(application); this.dataRepository = dataRepository; mZhihuList = Transformations.switchMap(requestPageDate, new Function<String, LiveData<List<ZhihuStory>>>() { @Override public LiveData<List<ZhihuStory>> apply(String input) { return dataRepository.getZhihuList(input); } }); }
public LiveData<List<ZhihuStory>> getZhihuList() { return mZhihuList; }
public LiveData<Boolean> isLoadingZhihuList() { return dataRepository.isLoadingZhihuList(); }
public void refreshZhihusData() { requestPageDate.setValue("today"); }
public void loadNextPageZhihu(int positon, Context context) { if (!Util.isNetworkConnected(context)) { return; } requestPageDate.setValue(String.valueOf(positon)); }
public static class Factory extends ViewModelProvider.NewInstanceFactory {
@NonNull private final Application application;
private final DataRepository dataRepository;
public Factory(@NonNull Application application, DataRepository dataRepository) { this.application = application; this.dataRepository = dataRepository; }
@Override public <T extends ViewModel> T create(Class<T> modelClass) { return (T) new ZhihuListViewModel(application, dataRepository); } } }
|
初始化时,对requestPageDate做监听,当requestPageDate的值有变化时,调用getZhihuList刷新列表的数据。列表刷新时回调中会调用refreshZhihusData(),此时会改变requestPageDate的值,也就会调用getZhihuList。列表加载更多时回调中会调用loadNextPageZhihu(int positon, Context context) ,也会改变requestPageDate的值,同样会调用getZhihuList。
接下来是DataRepository,数据源管理类,对应mvp的m层。
通过一个类注入数据源对象
1 2 3 4 5 6
| public class Injection { public static DataRepository getDataRepository(Application application) { return DataRepository.getInstance(RemoteDataSource.getInstance(), LocalDataSource.getInstance(application), application); } }
|
DataRepository单例类对网络数据源和本地数据源做统一管理
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| public class DataRepository {
private final DataSource remoteDataSource; private final DataSource localDataSource; private static DataRepository INSTANCE = null; private static Application application = null;
private DataRepository(@NonNull DataSource remoteDataSource, @NonNull DataSource localDataSource) { this.remoteDataSource = remoteDataSource; this.localDataSource = localDataSource; }
static DataRepository getInstance(@NonNull DataSource remoteDataSource, @NonNull DataSource localDataSource, Application app) { if (INSTANCE == null) { synchronized (DataRepository.class) { if (INSTANCE == null) { INSTANCE = new DataRepository(remoteDataSource, localDataSource); application = app; } } } return INSTANCE; }
public LiveData<List<ZhihuStory>> getZhihuList(@NonNull String date) { if (Util.isNetworkConnected(application.getApplicationContext())) { if (date.equals("today")) { return remoteDataSource.getLastZhihuList(); } else { return remoteDataSource.getMoreZhihuList(); } } else { if (date.equals("today")) { return localDataSource.getLastZhihuList(); } else { return localDataSource.getMoreZhihuList(); } } }
public LiveData<Boolean> isLoadingZhihuList() { if (Util.isNetworkConnected(application.getApplicationContext())) { return remoteDataSource.isLoadingZhihuList(); } else { return localDataSource.isLoadingZhihuList(); } } }
|
判断网络状态工具类
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Util { public static boolean isNetworkConnected(Context context) { if (context != null) { ConnectivityManager mConnectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); if (mNetworkInfo != null) { return mNetworkInfo.isAvailable(); } } return false; } }
|
网络数据源和本地数据源的统一接口
1 2 3 4 5
| public interface DataSource { LiveData<List<ZhihuStory>> getLastZhihuList(); LiveData<List<ZhihuStory>> getMoreZhihuList(); LiveData<Boolean> isLoadingZhihuList(); }
|
网络数据源单例类对网络请求做统一管理
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| public class RemoteDataSource implements DataSource { private static RemoteDataSource INSTANCE = null; private final MutableLiveData<Boolean> isLoadingZhihulist; private final MutableLiveData<List<ZhihuStory>> zhihuList; private String requestDate; private final ApiZhihu apiZhihu;
{ isLoadingZhihulist = new MutableLiveData<>(); zhihuList = new MutableLiveData<>(); }
private RemoteDataSource() { apiZhihu = ApiManager.getInstance().getApiZhihu(); }
public static RemoteDataSource getInstance() { if (INSTANCE == null) { synchronized (RemoteDataSource.class) { if (INSTANCE == null) { INSTANCE = new RemoteDataSource(); } } } return INSTANCE; }
@Override public LiveData<List<ZhihuStory>> getLastZhihuList() { isLoadingZhihulist.setValue(true); apiZhihu.getLatestNews() .enqueue(new Callback<ZhihuData>() { @Override public void onResponse(Call<ZhihuData> call, Response<ZhihuData> response) { if (response.isSuccessful()) { zhihuList.setValue(response.body().getStories()); refreshLocalZhihuList(response.body().getStories()); requestDate = response.body().getDate(); } isLoadingZhihulist.setValue(false); }
@Override public void onFailure(Call<ZhihuData> call, Throwable t) { isLoadingZhihulist.setValue(false); } }); return zhihuList; }
@Override public LiveData<List<ZhihuStory>> getMoreZhihuList() { isLoadingZhihulist.setValue(true); apiZhihu.getTheDaily(requestDate) .enqueue(new Callback<ZhihuData>() { @Override public void onResponse(Call<ZhihuData> call, Response<ZhihuData> response) { if (response.isSuccessful()) { zhihuList.setValue(response.body().getStories()); refreshLocalZhihuList(response.body().getStories()); requestDate = response.body().getDate(); } isLoadingZhihulist.setValue(false); }
@Override public void onFailure(Call<ZhihuData> call, Throwable t) { isLoadingZhihulist.setValue(false); } }); return zhihuList; }
@Override public MutableLiveData<Boolean> isLoadingZhihuList() { return isLoadingZhihulist; }
private void refreshLocalZhihuList(List<ZhihuStory> zhihuStoryList) { if (zhihuStoryList == null || zhihuStoryList.isEmpty()) { return; } AppDatabaseManager.getInstance().insertZhihuList(zhihuStoryList); } }
|
每次网络请求后,都需要刷新本地数据源的数据,ApiManager对api做统一管理,提供获取Retrofit网络请求对象的接口。
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 31 32 33 34 35 36 37 38
| public class ApiManager { private static final String ZHIHU_URL = "https://news-at.zhihu.com/";
private static ApiManager INSTANCE; private static ApiZhihu apiZhihu;
private ApiManager() { }
public static ApiManager getInstance() { if (INSTANCE == null) { synchronized (ApiManager.class) { if (INSTANCE == null) { INSTANCE = new ApiManager(); } } } return INSTANCE; }
public ApiZhihu getApiZhihu() { if (apiZhihu == null) { synchronized (ApiManager.class) { if (apiZhihu == null) { apiZhihu = new Retrofit.Builder() .baseUrl(ZHIHU_URL) .addConverterFactory(GsonConverterFactory.create()) .build() .create(ApiZhihu.class); } } } return apiZhihu; } }
|
定义Retrofit网络请求接口,代码如下:
1 2 3 4 5 6 7
| public interface ApiZhihu { @GET("api/4/news/latest") Call<ZhihuData> getLatestNews();
@GET("/api/4/news/before/{date}") Call<ZhihuData> getTheDaily(@Path("date") String date); }
|
本地数据源单例类做统一管理
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 31 32 33 34 35
| public class LocalDataSource implements DataSource {
private static LocalDataSource INSTANCE = null;
private LocalDataSource(Context context) { AppDatabaseManager.getInstance().createDB(context); }
public static LocalDataSource getInstance(Context context) { if (INSTANCE == null) { synchronized (LocalDataSource.class) { if (INSTANCE == null) { INSTANCE = new LocalDataSource(context); } } } return INSTANCE; }
@Override public LiveData<List<ZhihuStory>> getLastZhihuList() { return AppDatabaseManager.getInstance().loadZhihuList(); }
@Override public LiveData<List<ZhihuStory>> getMoreZhihuList() { return null; }
@Override public LiveData<Boolean> isLoadingZhihuList() { return AppDatabaseManager.getInstance().isLoadingZhihuList(); } }
|
AppDatabaseManager单例类是数据库的管理工具类,代码如下:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| public class AppDatabaseManager { private static final String DATABASE_NAME = "google-architecture-study-db"; private final MutableLiveData<Boolean> mIsLoadingZhihuList; private final MutableLiveData<List<ZhihuStory>> mZhihuList; private static AppDatabaseManager INSTANCE = null; private AppDatabase mDB = null;
{ mIsLoadingZhihuList = new MutableLiveData<>(); mZhihuList = new MutableLiveData<>(); }
private AppDatabaseManager() { }
public static AppDatabaseManager getInstance() { if (INSTANCE == null) { synchronized (AppDatabaseManager.class) { if (INSTANCE == null) { INSTANCE = new AppDatabaseManager(); } } } return INSTANCE; }
public void createDB(Context context) { new AsyncTask<Context, Void, Void>() { @Override protected Void doInBackground(Context... params) { Context context = params[0].getApplicationContext(); mDB = Room.databaseBuilder(context, AppDatabase.class, DATABASE_NAME).build(); return null; } }.execute(context.getApplicationContext()); }
public void insertZhihuList(final List<ZhihuStory> zhihuStoryList) { new AsyncTask<Void, Void, Void>(){ @Override protected Void doInBackground(Void... voids) { mDB.beginTransaction(); try { mDB.zhihuDao().insertZhihuList(zhihuStoryList); mDB.setTransactionSuccessful(); } catch (Exception e) { e.printStackTrace(); } finally { mDB.endTransaction(); } return null; } }.execute(); }
public LiveData<List<ZhihuStory>> loadZhihuList() { mIsLoadingZhihuList.setValue(true); new AsyncTask<Void, Void, List<ZhihuStory>>() { @Override protected List<ZhihuStory> doInBackground(Void... voids) { List<ZhihuStory> results = new ArrayList<>(); mDB.beginTransaction(); try { results.addAll(mDB.zhihuDao().loadAllZhihus()); mDB.setTransactionSuccessful(); } catch (Exception e) { e.printStackTrace(); } finally { mDB.endTransaction(); } return results; }
@Override protected void onPostExecute(List<ZhihuStory> aVoid) { super.onPostExecute(aVoid); mIsLoadingZhihuList.setValue(false); mZhihuList.setValue(aVoid); }
@Override protected void onCancelled(List<ZhihuStory> aVoid) { super.onCancelled(aVoid); mIsLoadingZhihuList.setValue(false); } }.execute(); return mZhihuList; }
public MutableLiveData<Boolean> isLoadingZhihuList() { return mIsLoadingZhihuList; } }
|
初始化数据库room
1 2 3 4 5
| @Database(entities = {ZhihuStory.class}, version = 1, exportSchema = false) @TypeConverters({Converters.class}) public abstract class AppDatabase extends RoomDatabase { public abstract ZhihuDao zhihuDao(); }
|
定义数据库room相关接口
1 2 3 4 5 6 7 8
| @Dao public interface ZhihuDao { @Query("SELECT * FROM zhihustorys") List<ZhihuStory> loadAllZhihus();
@Insert(onConflict = OnConflictStrategy.REPLACE) void insertZhihuList(List<ZhihuStory> zhihuStories); }
|
时间字符串拼接与切割转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Converters { @TypeConverter public static String fromTimestamp(String[] arrays) { StringBuilder stringBuilder = new StringBuilder(); for (String s : arrays) { stringBuilder.append(s).append(","); } int length = stringBuilder.toString().length(); if (length > 0) { stringBuilder = stringBuilder.deleteCharAt(length - 1); } return stringBuilder.toString(); }
@TypeConverter public static String[] dateToTimestamp(String string) { return string.split(","); } }
|
涉及到的dto,知乎资讯内层dto,需要增加一些注解,这里对应数据库的zhihustorys
的表,主键为id,代码如下:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| @Entity(tableName = "zhihustorys") public class ZhihuStory {
@PrimaryKey private String id;
private int type;
private String ga_prefix;
private String title;
private String[] images;
public ZhihuStory() { }
public ZhihuStory(ZhihuStory zhihuStory) { this.id = zhihuStory.getId(); this.type = zhihuStory.getType(); this.ga_prefix = zhihuStory.getGa_prefix(); this.title = zhihuStory.getTitle(); this.images = zhihuStory.getImages(); }
public String[] getImages() { return images; }
public void setImages(String[] images) { this.images = images; }
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public int getType() { return type; }
public void setType(int type) { this.type = type; }
public String getGa_prefix() { return ga_prefix; }
public void setGa_prefix(String ga_prefix) { this.ga_prefix = ga_prefix; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
|
知乎资讯外层dto,代码如下:
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 31
| public class ZhihuData { private String date;
private List<ZhihuStory> stories;
private List<ZhihuStory> top_stories;
public String getDate() { return date; }
public void setDate(String date) { this.date = date; }
public List<ZhihuStory> getStories() { return stories; }
public void setStories(List<ZhihuStory> stories) { this.stories = stories; }
public List<ZhihuStory> getTop_stories() { return top_stories; }
public void setTop_stories(List<ZhihuStory> top_stories) { this.top_stories = top_stories; } }
|
以上是基于Architecture Components 架构的展示知乎资讯列表的所有代码。
总结:Architecture Components的架构总体上看起来还是很像MVP+Rxjava,M层对应DataRepository,V层对应activity,P层对应AndroidViewModel。AndroidViewModel中注册了请求参数改变的监听,在回调中发起数据请求,V层调用P层是通过AndroidViewModel的方法修改对应的请求参数。activity中注册了AndroidViewModel中的列表数据改变监听,在回调中刷新列表控件,P层得到结果刷新V层,也是通过改变AndroidViewModel中的列表数据来触发回调刷新。所以P层和V层都没有抽象出接口。M层通过统一的数据源管理类分别管理网络数据源和本地数据源。网络数据源通过Retrofit做网络请求,本地数据源主要通过数据库存储实现,定义了一个room数据库工具类做统一管理。个人觉得就目前的版本而言,如果原本就是MVP+Rxjava这种架构的完全可以不用重构了。