0. 介绍
此文将对Github上lguipeng大神所开发的 极简笔记 v2.0
(点我下载源码)代码进行分析学习。
通过此文你将学到:
- 应用源码的研读方法
- MVP架构模式
- Application的应用
- Degger2依赖注入框架
- 搜索控件的使用
- ButterKnife库的使用
- Material主题
- RecyclerView等新控件的用法
- Lambda表达式
- Java自定义注解
- aFinal框架
- RxJava框架
- EventBus消息框架
- 布局文件常用技巧
- PreferenceFragment
- 动态申请权限
此为系列文章,传送门: 《极简笔记》源码分析(一)
3.3 onCreate()方法
3.3.1 Activity崩溃的信息保存
想要在Activity崩溃的时候,其实主要是屏幕发生旋转时保存信息,以便重启Activity后能够恢复信息,需要怎么做呢?很简单,重写 onSaveInstanceState(Bundle outState)
方法,在内部实现信息的保存,并在onCreate方法中对信息进行恢复即可,如:
// 信息的保存: 笔者猜测是用于保存当前界面,保存在"Normal"模式还是"回收站"模式
public void onSaveInstanceState(Bundle outState){
outState.putInt(CURRENT_NOTE_TYPE_KEY, mCurrentNoteTypePage.getValue());
}
然后再onCreate中恢复信息:
if (savedInstanceState != null){
int value = savedInstanceState.getInt(CURRENT_NOTE_TYPE_KEY);
mCurrentNoteTypePage = SNote.NoteType.mapValueToStatus(value);
}
3.3.2 初始化视图
view.initToolbar();
initDrawer();
initMenuGravity();
initItemLayoutManager();
initRecyclerView();
在onCreate方法中还进行了各视图的初始化。
3.3.2.1 initToolbar
调用所有继承自MainView接口的Activity的初始化Toolbar方法以初始化。
3.3.2.2 初始化抽屉
drawerList = Arrays.asList(mContext.getResources()
.getStringArray(R.array.drawer_content));
view.initDrawerView(drawerList);
view.setDrawerItemChecked(mCurrentNoteTypePage.getValue());
view.setToolbarTitle(drawerList.get(mCurrentNoteTypePage.getValue()));
这里值得留意的是在strings.xml中定义数组:
<array name="drawer_content">
<item>SNotes</item>
<item>回收站</item>
</array>
3.3.2.3 设置抽屉方向
通过view.setMenuGravity(Gravity.END)和view.setMenuGravity(Gravity.START)设置抽屉的方向,由MVP模式,具体的实现放在MainActivity中。
@Override
public void setMenuGravity(int gravity) {
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) drawerRootView.getLayoutParams();
params.gravity = gravity;
drawerRootView.setLayoutParams(params);
}
3.3.2.4 设置RecyclerView线性或网格排列
private void switchItemLayoutManager(boolean card){
if (card){
view.setLayoutManager(new StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL));
}else {
view.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));
}
isCardItemLayout = card;
}
在MainActivity中进行视图操作:
recyclerView.setLayoutManager(manager);
3.3.2.5 加载笔记
public void initRecyclerView(){
view.showProgressWheel(true);
mObservableUtils.getLocalNotesByType(mFinalDb, mCurrentNoteTypePage.getValue())
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((notes) -> {
view.initRecyclerView(notes);
view.showProgressWheel(false);
}, (e) -> {
e.printStackTrace();
view.showProgressWheel(false);
});
}
这段代码又用到RxJava,所以显得比较复杂,我们来慢慢分析。
3.3.2.5.1 getLocalNotesByType
那么首先来看ObservableUtils里的getLocalNotesByType方法,它将返回一个Observable对象,此处截取了关键部分:
return Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> subscriber) {
try {
T t = fun.call();
subscriber.onNext(t);
}catch (Exception e){
subscriber.onError(e);
}
}
});
下面是fun.call():
@Override
public List<SNote> call() throws Exception {
return mFinalDb.findAllByWhere(SNote.class, "type = " + type, "lastOprTime", true);
}
其中type为mCurrentNoteTypePage.getValue(),标志是否为回收站的内容。
findAllByWhere方法原型为:
List<T> findAllByWhere(Class<T> clazz, String strWhere, String orderBy, boolean desc);
它是aFinal库中FinalDb中的方法,功能为依据条件查找所有元素并反序列化为对象List。
3.3.2.5.2 指定线程
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
此处的代码用于指定观察者和被观察者所在的线程,由于加载笔记属于重大任务,所以指定在computation计算线程,另外subscribeOn可指定的参数如图:
此处回顾一下RxJava的调度器:
在RxJava 中,Scheduler ——调度器,相当于线程控制器,RxJava 通过它来指定每一段代码应该运行在什么样的线程。RxJava 已经内置了几个 Scheduler ,它们已经适合大多数的使用场景:
- Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler。
- Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
- Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
- Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
- 另外, Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行。
3.3.2.5.3 订阅
subscribe(onNext, onError)方法需要两个参数,一个是接下来需要执行的操作,一个是执行错误的回调操作,均需要实现Action1接口,此处使用lambda表达式简化了代码。
3.3.3 EventBus库
EventBus.getDefault().register(this);
接下来使用到了 EventBus
库,所以让我们先来学习一下EventBus,这个库真可以说是发现的一块新大陆。
3.3.3.1 介绍
EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。
3.3.3.2 使用
- 导入库
compile 'org.greenrobot:eventbus:3.0.0'
- 编写消息类,传递的消息是一个对象
public class FirstEvent {
private String mMsg;
public FirstEvent(String msg) {
mMsg = msg;
}
public String getMsg(){
return mMsg;
}
}
- 注册与反注册
在onCreate方法中进行注册:
EventBus.getDefault().register(this);
在onDestroy方法中进行反注册:
EventBus.getDefault().unregister(this);
- 发送消息
EventBus.getDefault().post(new FirstEvent("FirstEvent btn clicked"));
- 接收消息
在接受消息的Activity中重写事件接收方法,如onEventMainThread:
public void onEventMainThread(FirstEvent event);
注参数一定要匹配,否则该方法将接收不到数据。消息的接收是通过判断参数是否匹配来的,它将调用四种接收方法中所有匹配该参数的方法。
3.3.3.3 4种接收消息方法
前文在接受消息使用到是onEventMainThread方法,那么各方法有什么区别呢。
- onEvent:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。
- onEventMainThread:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
- onEventBackground:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
- onEventAsync:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.
至此,EventBus的使用已基本介绍完毕,接下来分析EventBus在本项目中的使用。
3.3.3.3 本项目中EventBus使用
3.3.3.3.1 注册与反注册
首先在onCreate和onDestroy中进行了注册与反注册。
3.3.3.3.2 消息接收——同步笔记
public void onEventMainThread(EverNoteUtils.SyncResult result){
if (result != EverNoteUtils.SyncResult.START)
view.stopRefresh();
switch (result){
case ERROR_NOT_LOGIN: view.showGoBindEverNoteSnackbar(R.string.unbind_ever_note_tip, R.string.go_bind);break;
...
case SUCCESS:view.showSnackbar(R.string.sync_success);refreshNoteTypePage();break;
}
}
消息接收方法处于主线程