Dependency Injection with Dagger 2

Dependency Injection with Dagger 2

原文:Dependency Injection with Dagger 2

创建单例

下面展示一个简单的Dagger来管理所有单例创建的例子:

Dagger2 使用

Dagger

Dagger 是一款依赖注入框架。

项目地址:dagger

官方文档:Dagger

依赖注入

依赖注入(Dependency Injection),简称DI,又叫控制反转(Inversion of Control),简称IOC。
当一个类的实例需要另另一个类的实例进行协助时,在传统的设计中,通常由调用者来创建被调用者的实例,然而依赖注入的方式,创建被调用者不再由调用者创建实例,创建被调用者的实例的工作由IOC容器来完成,然后注入到调用者。因此也被称为依赖注入。

作用:将各层的对象以松耦合的方式组织在一起,解耦,各层对象的调用完全面向接口。当系统重构或者修改的时候,代码的改写量将大大减少。

Android Studio 引入Dagger2

1
2
3
4
implementation 'com.google.dagger:dagger-android:2.17'
implementation 'com.google.dagger:dagger-android-support:2.17'
annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'

2018 end

2018 总结

转眼间 2018 已经结束了。还是很丰富的一年。

我是一个仪式感很强的人,临近新年时,并没有别人那种期待欢喜的感觉,却总是有一种离别的失落感。

工作

5 月底的时候终于离职了,在经过一个月的项目交接之后我离开了毕业后加入的第一家公司,对老东家谈不上感激,大家也就是好聚好散。这算是我第一份正式的工作,还是学到了很多的东西的,包括技术、项目、合作等等,因为对未来的规划以及薪资上的问题 3 月底就确定要离职,之后一直在准备面试。因为准备的时间很长,准备的也比较充分,两天就拿到两个 offer,虽然都不是大厂,其中选择了一家比较有前景的公司就直接入职了。后来觉得太着急了,其实该多面试找找机会的。在公司的这半年时间里,经历了还是很多的,封闭开发,两个月的调休期,拖欠工资,公司倒闭…..好充实的。

Python 还是一直在学的,但是效率太低,发现问题在于只是看书或者教程,自己很少写代码,而且完全没有在工作中用到。后来自己试着去写 wallhaven 的爬虫,学着去操作数据库,尝试 web开发。未来学习的方向想放在商业化爬虫以及数据可视化处理上。

Android 还是一直在发展的,今年的主要研究方向在项目框架、Kotlin、RxJava 还有 Dagger,“纸上得来终觉浅 ” 在学习的过程中有时候感觉这些知识点自己已经掌握了,但是到了自己实际使用的时候还是会出现很多的问题,还有就是学完之后一定要在项目中去使用,去试着思考使用场景、bug、设计模式等等,否则遗忘的时间是很快的。在学习 Kotlin,Python 以及阅读 Github 优秀开源项目的时候,逐渐理解了所谓的编程思想,总之学无止境,还有很多需要进步的地方。

Android 行业今年有些疲软,工作岗位已经少了很多了,但我相信“高级程序员”的数量还是很少的,暂时还是打算继续走这条路。今年有一点小小的学习感触,一段时间内最好是只学习一个知识点,大量的寻找相关的博客、教程等等,关键的地方一定要做笔记,有一段时间是很浮躁的,因为发现有特别多需要学习的,又没有好的学习方式,所以导致心态有些问题。后来看到胡适先生的一句话“怕什么真理无穷,进一寸有一寸的欢喜”,豁然开朗。

Android 屏幕适配

屏幕适配

前言

由于Android系统的开放性,任何用户、开发者、硬件厂商、运营商都可以对Android系统和硬件进行定制,修改成他们想要的样子。 那么这种“碎片化”到达什么程度呢?

Android屏幕碎片化

以上每一个矩形都代表一种机型,且它们屏幕尺寸、屏幕分辨率大相径庭。随着Android设备的增多,设备碎片化、系统碎片化、屏幕尺寸碎片化、屏幕碎片化的程度也在不断加深。

当 Android 系统、屏幕尺寸、屏幕密度出现碎片化的时候,就很容易出现同一元素在不同手机上显示不同的问题。试想一下这么一个场景: 为 4.3 寸屏幕准备的 UI 设计图,运行在 5.0 寸的屏幕上,很可能在右侧和下侧存在大量的空白;而 5.0 寸的 UI 设计图运行到 4.3 寸的设备上,很可能显示不下。

为了保证用户获得一致的用户体验效果,使得某一元素在 Android 不同尺寸、不同分辨率的、不同系统的手机上具备相同的显示效果,能够保持界面上的效果一致,我们需要对各种手机屏幕进行适配!

基本概念

像素(px):

单位:px(pixel),像素就是手机屏幕的最小构成单元

分辨率

px(pixel),1px = 1像素点,手机在横向、纵向上的像素点数总和 一般描述成 宽高 ,即横向像素点个数 纵向像素点个数(如1080 x 1920)。

屏幕尺寸

单位 英寸(inch),手机对角线的物理尺寸

屏幕像素密度

单位:dpi,每英寸的像素点数。 例如每英寸内有 160 个像素点,则其像素密度为 160dpi。计算公式:像素密度 = 像素 / 尺寸 (dpi = px / in)

密度无关像素

density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。单位:dp,可以保证在不同屏幕像素密度的设备上显示相同的效果,是安卓特有的长度单位。dp 与 px 的转换:1dp = (dpi / 160 ) * 1px

独立比例像素

scale-independent pixel,叫sp或sip。单位:sp,字体大小专用单位 Android开发时用此单位设置文字大小。

DPI 的存在,不就是为了让大屏能显示更多的内容

适配方案

今日头条

参考项目地址:https://github.com/JessYanCoding/AndroidAutoSize

今日头条适配方案默认项目中只能以高或宽中的一个作为基准。

density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以获得,而Resouces 通过 Activity 或者 Application 的 Context 获得。

如果每个 Viewdp 值是固定不变的,那我们只要保证每个设备的屏幕总 dp 宽度不变,就能保证每个 View 在所有分辨率的屏幕上与屏幕的比例都保持不变,从而完成等比例适配,并且这个屏幕总 dp 宽度如果还能保证和设计图的宽度一致的话,那我们在布局时就可以直接按照设计图上的尺寸填写 dp

屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度

在这个公式中我们要保证 屏幕的总 dp 宽度设计图总宽度 一致,并且在所有分辨率的屏幕上都保持不变,我们需要怎么做呢?屏幕的总 px 宽度 每个设备都不一致,这个值是肯定会变化的,这时今日头条的公式就派上用场了

当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density

这个公式就是把上面公式中的 屏幕的总 dp 宽度 换成 设计图总宽度,原理都是一样的,只要 density 根据不同的设备进行实时计算并作出改变,就能保证 设计图总宽度 不变,也就完成了适配。

布局文件中 dp 的转换,最终都是调用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 来进行转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static float applyDimension(int unit, float value, DisplayMetrics metrics){
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}

这里用到的 DisplayMetrics 正是从 Resources 中获得的。图片的decode,也是通过 DisplayMetrics 中的值来计算的。

当然还有些其他 dp 转换的场景,基本都是通过 DisplayMetrics 来计算的,这里不再详述。因此,想要满足上述需求,我们只需要修改 DisplayMetrics 中和 dp 转换相关的变量即可。

适配后的 density = 设备真实宽(单位px) / 360,接下来只需要把我们计算好的 density 在系统中修改下即可,代码实现如下:

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
private static float sNonCompatDensity;
private static float sNonCompatScaledDensity;

private static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application) {
// 设计图宽度
int customWidth = 360;
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();

if (sNonCompatDensity == 0) {
sNonCompatDensity = appDisplayMetrics.density;
sNonCompatScaledDensity = appDisplayMetrics.scaledDensity;
// 监听系统字体变化
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
sNonCompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}

@Override
public void onLowMemory() {

}
});
}

final float targetDensity = appDisplayMetrics.widthPixels / customWidth;
final float targetScaledDensity = targetDensity * (sNonCompatScaledDensity / sNonCompatDensity);
final int targetDensityDpi = (int) (160 * targetDensity);

appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaledDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;

DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}

优点

  • 使用成本非常低,操作非常简单,使用该方案后在页面布局时不需要额外的代码和操作,这点可以说完虐其他屏幕适配方案。
  • 侵入性非常低,该方案和项目完全解耦,在项目布局时不会依赖哪怕一行该方案的代码,而且使用的还是 Android 官方的 API,意味着当你遇到什么问题无法解决,想切换为其他屏幕适配方案时,基本不需要更改之前的代码,整个切换过程几乎在瞬间完成,会少很多麻烦,节约很多时间,试错成本接近于 0。
  • 可适配三方库的控件和系统的控件(不止是是 ActivityFragmentDialogToast 等所有系统控件都可以适配),由于修改的 density 在整个项目中是全局的,所以只要一次修改,项目中的所有地方都会受益。
  • 不会有任何性能的损耗

缺点

会影响第三方控件

解决方案:

  1. 我们自身作出修改,去适应三方库的设计图尺寸,我们将项目自身的设计图尺寸修改为这个三方库的设计图尺寸,就能完成项目自身和三方库的适配
  2. Activity 为单位,取消当前 Activity 的适配效果,改用其他的适配方案

SmallestWidth

这个方案的的使用方式和我们平时在布局中引用 dimens 无异,核心点在于生成 dimens.xml 文件,但是已经有大神帮我们做了这 一步

1
2
3
4
5
6
7
8
9
├── src/main
│ ├── res
│ ├── ├──values
│ ├── ├──values-800x480
│ ├── ├──values-860x540
│ ├── ├──values-1024x600
│ ├── ├──values-1024x768
│ ├── ├──...
│ ├── ├──values-2560x1440

如果有人还记得上面这种 宽高限定符屏幕适配方案 的话,就可以把 smallestWidth 限定符屏幕适配方案 当成这种方案的升级版,smallestWidth 限定符屏幕适配方案 只是把 dimens.xml 文件中的值从 px 换成了 dp,原理和使用方式都是没变的,这些在上面的文章中都有介绍,下面就直接开始剖析原理,smallestWidth 限定符屏幕适配方案 长这样

1
2
3
4
5
6
7
8
9
10
11
├── src/main
│ ├── res
│ ├── ├──values
│ ├── ├──values-sw320dp
│ ├── ├──values-sw360dp
│ ├── ├──values-sw400dp
│ ├── ├──values-sw411dp
│ ├── ├──values-sw480dp
│ ├── ├──...
│ ├── ├──values-sw600dp
│ ├── ├──values-sw640dp

其实 smallestWidth 限定符屏幕适配方案 的原理也很简单,开发者先在项目中根据主流屏幕的 最小宽度 (smallestWidth) 生成一系列 values-swdp 文件夹 (含有 dimens.xml 文件),当把项目运行到设备上时,系统会根据当前设备屏幕的 最小宽度 (smallestWidth) 去匹配对应的 values-swdp 文件夹,而对应的 values-swdp 文件夹中的 dimens.xml 文字中的值,又是根据当前设备屏幕的 最小宽度 (smallestWidth) 而定制的,所以一定能适配当前设备。

如果系统根据当前设备屏幕的 最小宽度 (smallestWidth) 没找到对应的 values-swdp 文件夹,则会去寻找与之 最小宽度 (smallestWidth) 相近的 values-swdp 文件夹,系统只会寻找小于或等于当前设备 最小宽度 (smallestWidth)values-swdp,这就是优于 宽高限定符屏幕适配方案 的容错率,并且也可以少生成很多 values-swdp 文件夹,减轻 App 的体积。

优点

  1. 非常稳定,极低概率出现意外
  2. 不会有任何性能的损耗
  3. 适配范围可自由控制,不会影响其他三方库
  4. 在插件的配合下,学习成本低

缺点

  1. 在布局中引用 dimens 的方式,虽然学习成本低,但是在日常维护修改时较麻烦
  2. 侵入性高,如果项目想切换为其他屏幕适配方案,因为每个 Layout 文件中都存在有大量 dimens 的引用,这时修改起来工作量非常巨大,切换成本非常高昂
  3. 无法覆盖全部机型,想覆盖更多机型的做法就是生成更多的资源文件,但这样会增加 App 体积,在没有覆盖的机型上还会出现一定的误差,所以有时需要在适配效果和占用空间上做一些抉择
  4. 如果想使用 sp,也需要生成一系列的 dimens,导致再次增加 App 的体积
  5. 不能自动支持横竖屏切换时的适配,如上文所说,如果想自动支持横竖屏切换时的适配,需要使用 values-wdp屏幕方向限定符 再生成一套资源文件,这样又会再次增加 App 的体积
  6. 不能以高度为基准进行适配,考虑到这个方案的名字本身就叫 最小宽度限定符适配方案,所以在使用这个方案之前就应该要知道这个方案只能以宽度为基准进行适配,为什么现在的屏幕适配方案只能以高度或宽度其中的一个作为基准进行适配

AndroidAutoSize

根据 今日头条屏幕适配方案 优化的屏幕适配框架。

其他方案

UI适配框架

例如 Android 屏幕适配方案,不过已经停止维护

宽高限定符适配

1
2
3
4
5
6
7
8
9
├── src/main
│ ├── res
│ ├── ├──values
│ ├── ├──values-800x480
│ ├── ├──values-860x540
│ ├── ├──values-1024x600
│ ├── ├──values-1024x768
│ ├── ├──...
│ ├── ├──values-2560x1440

Android 屏幕适配终结者

Android 屏幕适配终结者 ,也是基于头条的原理,不过是操作 pt,所以不是改 DisplayMetrics#density,而是 DisplayMetrics#xdpi,由于适配不会失效

参考资料

RxJava系列(一) 入门

RxJava 入门

  1. 入门(概念、基础使用)
  2. 进阶(操作符(map、flatmap、zip、defer、contatMap等等),背压等)
  3. 实战(网络请求等)
  4. 源码解析(变换、线程调度,源码探讨)

概念

下面是摘自 ReactiveX 官网 的一段话。

ReactiveX is a library for composing asynchronous and event-based programs by using observable sequences.(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)

其中提到了几个概念,观察者模式、响应式编程。

响应式编程

概念:响应式编程是一种通过异步和数据流来构建事物关系的编程模型。

一般的编码模式中,“人”在其中扮演了过重的角色,关心程序中的每一部分。某种意义上这是一种顺序性思维的编程,我要做什么,然后做什么,最后做什么,按部就班编写就好了。具体如下图:

顺序性思维

而响应式编程,全都是事物与事物之间的关系,解脱了”人”,之后一个事物发生变化另一个事物就自动响应。如下:

响应式编程

个人感觉响应式编程就是用异步数据流进行编程。流是响应式的核心,可以基于任何东西创建数据流,响应式编程就是根据数据流的流向进行一系列的操作。

观察者模式

观察者模式(Observer Pattern):定义了对象间的一种一对多的依赖关系,当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

观察者模式面向的需求是:A 对象(观察者)对 B 对象(被观察者)的某种变化高度敏感,需要在 B 变化的一瞬间做出反应。举个例子,新闻里喜闻乐见的警察抓小偷,警察需要在小偷伸手作案的时候实施抓捕。在这个例子里,警察是观察者,小偷是被观察者,警察需要时刻盯着小偷的一举一动,才能保证不会漏过任何瞬间。程序的观察者模式和这种真正的『观察』略有不同,观察者不需要时刻盯着被观察者(例如 A 不需要每过 2ms 就检查一次 B 的状态),而是采用 注册(Register) 或者称为 订阅(Subscribe) 的方式,告诉被观察者:我需要你的某某状态,你要在它变化的时候通知我。 Android 开发中一个比较典型的例子是点击监听器 OnClickListener 。对设置 OnClickListener 来说, View 是被观察者, OnClickListener 是观察者,二者通过 setOnClickListener() 方法达成订阅关系。订阅之后用户点击按钮的瞬间,Android Framework 就会将点击事件发送给已经注册的 OnClickListener 。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高的反馈速度。当然,这也得益于我们可以随意定制自己程序中的观察者和被观察者,而警察叔叔明显无法要求小偷『你在作案的时候务必通知我』。

RxJava 简介

我所理解的RxJava的核心优势应该是它可以对复杂逻辑进行拆分成为一个一个的Observable后,RxJava的各种操作符予这些解耦的Observable能够合理的进行再组织的能力,并且它给予了你足够丰富的再组织能力。这种分拆再组织的能力是十分强大的,只有运用好RxJava这种强大的能力,才能真正意义上使你原来非常复杂的揉在一团的逻辑代码变得清晰、简洁,本质上是因为RxJava给你提供了这种强大方便的组织能力,我觉得有点像一种编程模式,你可以放心的将复杂的逻辑拆块,最后RxJava给你提供了丰富的组织、变换、串联、控制这些块的能力,只有这个时候你才会真正觉得这是个好东西,而不应该是跟风使用,但是心里也说不清楚为什么要使用。

RxJava 有三个基本概念: Observable (被观察者),Observer (观察者),subscribe (订阅)。ObservableObserver 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer

Github 链接:

RxJava https://github.com/ReactiveX/RxJava

RxAndroid https://github.com/ReactiveX/RxAndroid

gradle 依赖:

implementation 'io.reactivex.rxjava2:rxjava:2.x.y'

implementation 'io.reactivex.rxjava2:rxandroid:2.x.y'

RxJava 使用

创建被观察者

1
2
3
4
5
6
7
8
9
Observable<String> observable = Observable
.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) {
emitter.onNext("Hello");
emitter.onNext("World");
emitter.onComplete();
}
});

可以看到,这里传入了一个 ObservableOnSubscribe 对象作为参数。ObservableOnSubscribe 会被存储在返回的 Observable 对象中,当 Observable 被订阅的时候,subscribe 方法就会自动被调用,事件序列就会依照设定依次触发(对于上面的代码,就是观察者 observer 将会被调用三次 onNext() 和一次 onComplete())。

此外,还可以通过其他方法来创建被观察者。

  • just(T...): 将传入的参数依次发送出来。

    1
    Observable observable = Observable.just("Hello", "World");
  • fromArray(T...items) : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。

    1
    2
    String[] words = {"Hello", "Hi", "Aloha"};
    Observable<String> observable = Observable.fromArray(words);

Observable 外还有 Flowable 等被观察者类型。

创建观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Observer<String> observer = new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(String s) {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onComplete() {

}
};

在观察者中进行响应事件对应的相关操作。

订阅

1
observable.subscribe(observer);

这里的写法是被观察者订阅了观察者,而不是观察者订阅被观察者,是为了保证流式API调用风格。

1
2
3
4
5
observable
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.filter(s -> s != null)
.subscribe(observer);

上面就是一个非常简易的RxJava流式API的调用:同一个调用主体一路调用下来,一气呵成。

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
//创建被观察者,是事件传递的起点
Observable.just("On","Off","On","On")
//这就是在传递过程中对事件进行过滤操作
.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
return s!=null;
}
})
//实现订阅
.subscribe(
//创建观察者,作为事件传递的终点处理事件
new Subscriber<String>() {
@Override
public void onCompleted() {
Log.d("DDDDDD","结束观察...\n");
}

@Override
public void onError(Throwable e) {
//出现错误会调用这个方法
}
@Override
public void onNext(String s) {
//处理事件
Log.d("DDDDD","handle this---"+s)
}
);

注意:当调用订阅操作(即调用Observable.subscribe()方法)的时候,被观察者才真正开始发出事件。

线程调度

至此,在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的。

在 RxJava 中,通过 Scheduler 来指定每一段代码应该运行在什么样的线程。下表展示了RxJava中可用的调度器种类:

调度器类型 效果
Schedulers.computation( ) 用于计算任务,如事件循环或和回调处理,不要用于IO操作(IO操作请使用Schedulers.io());默认线程数等于处理器的数量
Schedulers.from(executor) 使用指定的Executor作为调度器
Schedulers.immediate( ) 在当前线程立即开始执行任务
Schedulers.io( ) 用于IO密集型任务,如异步阻塞IO操作,这个调度器的线程池会根据需要增长;对于普通的计算任务,请使用Schedulers.computation();Schedulers.io( )默认是一个CachedThreadScheduler,很像一个有线程缓存的新线程调度器
Schedulers.newThread( ) 为每个任务创建一个新线程
Schedulers.trampoline( ) 当其它排队的任务完成后,在当前线程排队开始执行
AndroidSchedulers.mainThread() Android 主线程

subscribeOn(): 指定 subscribe() 所发生的线程,或者叫做事件产生的线程。

observeOn(): 指定 Observer 所运行在的线程。或者叫做事件消费的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//new Observable.just()执行在新线程
Observable.just(getFilePath())
//指定在新线程中创建被观察者
.subscribeOn(Schedulers.newThread())
//将接下来执行的线程环境指定为io线程
.observeOn(Schedulers.io())
//map就处在io线程
.map(mMapOperater)
//将后面执行的线程环境切换为主线程,
//但是这一句依然执行在io线程
.observeOn(AndroidSchedulers.mainThread())
//指定线程无效,但这句代码本身执行在主线程
.subscribeOn(Schedulers.io())
//执行在主线程
.subscribe(mSubscriber);

注意:

  • subscribeOn() 它指示 Observable 在一个指定的调度器上创建(只作用于被观察者创建阶段)。只能指定一次,如果指定多次则以第一次为准
  • observeOn() 指定在事件传递(加工变换)和最终被处理(观察者)的发生在哪一个调度器。可指定多次,每次指定完都在下一步生效。

=======================================================

进阶(操作符(map、flatmap、zip、defer、contatMap等等),背压等)

操作符 说明
Create 使用一个函数从头开始创建一个Observable
Defer 直到有观察者订阅时才创建Observable,并且为每个观察者创建一个新的Observable
Empty 创建一个不发射任何数据但是正常终止的Observable
Never 创建一个不发射数据也不终止的Observable
Throw 创建一个不发射数据以一个错误终止的Observable
From 将其它种类的对象和数据类型转换为Observable
Interval 创建一个按固定时间间隔发射整数序列的Observable
Just 创建一个发射指定值的Observable
Range 创建一个发射特定整数序列的Observable
Repeat 创建一个发射特定数据重复多次的Observable
Start 返回一个Observable,它发射一个类似于函数声明的值
Timer 创建一个Observable,它在一个给定的延迟后发射一个特殊的值
操作符 说明
操作符 说明

=======================================================

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
private void testRxJava() {
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) {

}
}).subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(String s) {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onComplete() {

}
})

// 伪代码
Observable.create( ObservableOnSubscribe(ObservableEmitter -> *) ){
ObservableCreate<T>(observableOnSubscribe)
}
.subscribe(Observer){
subscribeActual(observer){
parent = new CreateEmitter<T>(observer)
observer.onSubscribe(parent);
observableOnSubscribe.subscribe(parent);
}
}
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

// subscribeOn 是改变上流的线程调度,所以只有第一个 subscribeOn 是有用的
// observeOn 是改变下流的线程调度,所以每一个 observeOn 对它下流的 操作都是有用的
ObservableCreate subscribeActual(SubscribeOnObserver){ ObservableOnSubscribe.subscribe(observableEmitter) }
ObservableSubscribeOn subscribeActual(ObserveOnObserver)
ObservableObserveOn subscribeActual(MapObserver)
ObservableMap subscribeActual(SubscribeOnObserver)
ObservableSubscribeOn subscribeActual(ObserveOnObserver)
ObservableObserveOn subscribeActual(MergeObserver)
ObservableFlatMap subscribeActual(Observer)

subscribe(Observer)



Observable.create(...)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(s -> s + "!")
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(Function<String, ObservableSource<String>>) s -> Observable.just(s + "!"))
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(String s) {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onComplete() {

}
});

参考资料:

todo

todo

列一下目标清单上的一些计划。

技术

android

  • RxJava(源码解析、实战应用、操作符、设计模式)
  • Kotlin(一些面试问题,高级特性)
  • dagger(使用)
  • Jetpack(详解以及使用)
  • 高级(源码,bug,APT)
  • 架构(设计模式,项目框架封装)
  • 源码解析(OkHttp、Glide、EventBus、ButterKnife)

Python

  • Django 项目使用
  • Scrapy 使用
  • 特性学习
  • 500 Lines or Less
  • 数据可视化

计算机网络

数据结构

生活

  • 摄影(手机摄影)
  • PS

健身

  • 跑步

  • 流畅的Python
  • Android开发艺术探索
  • Head First 设计模式
  • 代码整洁之道
  • HTTP 权威指南
  • 万历十五年
  • 王小波全集

Android流行ORM框架

Android ORM框架

Room

1
2
3
4
def room_version = "1.1.1"
implementation "android.arch.persistence.room:runtime:$room_version"
annotationProcessor "android.arch.persistence.room:compiler:$room_version"
implementation "android.arch.persistence.room:rxjava2:$room_version"
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
@Entity(tableName = "user")
public class User {

@PrimaryKey
private int id;

@ColumnInfo(name = "first_name")
private String firstName;

@ColumnInfo(name = "last_name")
private String lastName;

@Ignore
private String nickName;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Dao
public interface UserDao {

@Query("select * from user")
List<User> getAll();

@Query("select * from user where id in (:ids)")
List<User> queryUserByIds(int[] ids);

@Query("select * from user where first_name like :first and last_name like :last limit 1")
User findByName(String first, String last);

@Insert(onConflict = REPLACE)
void insertAll(User... users);

@Delete
void delete(User user);

@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);
}
1
2
3
4
5
6
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

public abstract UserDao userDao();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private fun testRoom() {
val db = Room.databaseBuilder(applicationContext,
AppDatabase::class.java, "database-name").build()

var all = db.userDao().all

val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, " + "`name` TEXT, PRIMARY KEY(`id`))")
}
}
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Book "
+ " ADD COLUMN pub_year INTEGER")
}
}
Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database-name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()
}

GreenDao

Realm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// product
buildscript {
repositories {
...
maven {url 'http://oss.jfrog.org/artifactory/oss-snapshot-local'}
}
dependencies {
...
classpath "io.realm:realm-gradle-plugin:5.8.0-SNAPSHOT"
}
}

allprojects {
repositories {
...
maven {url 'http://oss.jfrog.org/artifactory/oss-snapshot-local'}
}
}


// module
apply plugin: 'realm-android'
1
2
3
4
5
6
7
8
9
10
class BaseApplication : Application() {

override fun onCreate() {
super.onCreate()
Realm.init(this);
val config = RealmConfiguration.Builder().build()
Realm.setDefaultConfiguration(config)
}

}
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
// 新建一个对象,并进行存储
private fun testCreateRealm() {
val realm = Realm.getDefaultInstance()
realm.beginTransaction()
val user = realm.createObject(User::class.java)
user.firstName = "lalla"
user.lastName = "aaaa"
realm.commitTransaction()
}

// 复制一个对象到Realm数据库
private fun copyToRealm() {
val realm = Realm.getDefaultInstance()

val user = User()
user.firstName = "lalla"
user.lastName = "aaaa"

realm.beginTransaction()
realm.copyToRealm(user)
realm.commitTransaction()
}

// 使用事务块
private fun testExecuteTransaction() {
val realm = Realm.getDefaultInstance()
val user = realm.createObject(User::class.java)
user.firstName = "lalla"
user.lastName = "aaaa"

realm.executeTransaction {
it.copyToRealm(user)
}
}

private fun testDelete() {
val realm = Realm.getDefaultInstance()
val users = realm.where(User::class.java).findAll()
realm.executeTransaction {
val user = users[1]
user?.deleteFromRealm()
//删除第一个数据
users.deleteFirstFromRealm()
//删除最后一个数据
users.deleteLastFromRealm()
//删除位置为1的数据
users.deleteFromRealm(1)
//删除所有数据
users.deleteAllFromRealm()
}
}

private fun testUpdate() {
val realm = Realm.getDefaultInstance()
val user = realm.where(User::class.java).equalTo("id", "1").findFirst()
realm.beginTransaction()
user?.firstName = "1111"
realm.commitTransaction()
}

private fun testQuery() {
val realm = Realm.getDefaultInstance()
val users = realm.where(User::class.java).findAll()

var average = users.average("age")
var max = users.max("age")
var sort = users.sort("age")

val user = realm.where(User::class.java).equalTo("id", "1").findFirst()
val user1 = realm.where(User::class.java).between("age", 1, 10).findFirst()
val user2 = realm.where(User::class.java).lessThan("id", 1).findFirst()
val user3 = realm.where(User::class.java).contains("id", "1").findFirst()
}

DBFlow

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
// product
allprojects {
repositories {
...
maven { url "https://www.jitpack.io" }
}
}

// module
dependencies {
// if Java use this. If using Kotlin do NOT use this.
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:${dbflow_version}"

// Use if Kotlin user.
kapt "com.github.Raizlabs.DBFlow:dbflow-processor:${dbflow_version}"

implementation "com.github.Raizlabs.DBFlow:dbflow-core:${dbflow_version}"
implementation "com.github.Raizlabs.DBFlow:dbflow:${dbflow_version}"

// sql-cipher database encryption (optional)
implementation "com.github.Raizlabs.DBFlow:dbflow-sqlcipher:${dbflow_version}"
implementation "net.zetetic:android-database-sqlcipher:${sqlcipher_version}@aar"

// kotlin extensions
implementation "com.github.Raizlabs.DBFlow:dbflow-kotlinextensions:${dbflow_version}"

// RXJava 2 support
implementation "com.github.Raizlabs.DBFlow:dbflow-rx2:${dbflow_version}"

// RXJava 2 Kotlin Extensions Support
implementation "com.github.Raizlabs.DBFlow:dbflow-rx2-kotlinextensions:${dbflow_version}"
}
1
2
3
4
5
6
7
8
class BaseApplication : Application() {

override fun onCreate() {
super.onCreate()
FlowManager.init(this)
}

}
1
2
3
4
5
6
7
8
9
@Database(version = VERSION, name = NAME)
public class DBFlowDataBase {

//数据库名称
static final String NAME = "RuomizDataBase";
//数据库版本
static final int VERSION = 1;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Table(database = DBFlowDataBase.class)
public class DBFlowModel extends BaseModel {

@PrimaryKey(autoincrement = true)
public int id;

@Column
public String name;

@Column
public int age;

@Column
public String address;

@Column
public String phone;

}

编译之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private fun testDBFlow() {
val dbFlowModel = DBFlowModel()
dbFlowModel.name = "Ruomiz"
dbFlowModel.address = "beijing"
dbFlowModel.age = 100
dbFlowModel.phone = "13333333333"
dbFlowModel.save()
dbFlowModel.update()
dbFlowModel.delete()
dbFlowModel.insert()

//根据name 单个查询
val dbFlowModel1 = Select().from(DBFlowModel::class.java).where(DBFlowModel_Table.name.`is`(name)).querySingle()

//单个查询
val dbFlowDataBase = Select().from(DBFlowDataBase::class.java).querySingle()
//返回所有结果
val dbFlowDataBases = Select().from(DBFlowDataBase::class.java).queryList()
}

OrmLite

下载地址:http://ormlite.com/releases/

需要下载最新的 core.jar 和 android.jar 并添加依赖

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
@DatabaseTable(tableName = "user")
public class User {

@DatabaseField(generatedId = true)//表示id为主键且自动生成
private int id;

@DatabaseField(columnName = "first_name")
private String firstName;

@DatabaseField(columnName = "last_name")
private String lastName;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}
}
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
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {

private static final String TABLE_NAME = "sqlite-test.db";
/**
* userDao ,每张表对于一个
*/
private Dao<User, Integer> userDao;

public DatabaseHelper(Context context, String databaseName,
CursorFactory factory, int databaseVersion) {
super(context, databaseName, factory, databaseVersion);
}

@Override
public void onCreate(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource) {
try {
TableUtils.createTable(connectionSource, User.class);
} catch (SQLException e) {
e.printStackTrace();
}

}

@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource,
int oldVersion, int newVersion) {
try {
TableUtils.dropTable(connectionSource, User.class, true);
onCreate(sqLiteDatabase, connectionSource);
} catch (SQLException e) {
e.printStackTrace();
}
}

public void insertUser(User user) throws SQLException {
getUserDao().create(user);
}

public void deleteUserById(int id, User user) throws SQLException {
getUserDao().deleteById(id);
getUserDao().delete(user);
}

public void updateData() throws SQLException {
UpdateBuilder<User, Integer> updateBuilder = getUserDao().updateBuilder();
updateBuilder.setWhere(updateBuilder.where().eq("name", "jack").and().gt("age", 19));
updateBuilder.updateColumnValue("name", "Jack");
updateBuilder.updateColumnValue("phone", "1111765765");
updateBuilder.update();
}

public void query() throws SQLException {
List<User> users = getUserDao().queryForAll();

User user = getUserDao().queryForId(3);

List<User> jsckUser = getUserDao().queryForEq("name", "Jack");
}

/**
* 获得userDao
*/
public Dao<User, Integer> getUserDao() throws SQLException {
if (userDao == null) {
userDao = getDao(User.class);
}
return userDao;
}

/**
* 释放资源
*/
@Override
public void close() {
super.close();
userDao = null;
}

}

分析及对比

观念大概是从 PGONE 被封杀开始转变的。当时也跟微博上那些吃瓜群众一样,拍手叫好,喜闻乐见。是对嘻哈没有什么兴趣,倒不是说讨厌他,只是讨厌她的粉丝,就像之前的薛之谦,吹的太过,捧得太高,但更多的是有人的一种习性吧,见不得这些,或许是有些嫉妒?更多人是选择去落井下石,补上一脚。当然也有我。清楚的记得当时有人为这种 “处罚” 鸣不平,当然在那个时候,我是不会在意这些腔调的。直到后来,参加了一期《歌手》的 GAI ,突然被退赛,一夜之间,嘻哈全面被封杀,这是才真正明白,为什么当时有些人会鸣不平,不只是 PGONE 的粉丝,还有些人看到了未来,被ZF干预。封杀这件事,如果你说他是违了法,好,说出来具体违反了什么法律法规,然后按相关规章制度办事,大家都就没有异议的。但是只是凭上层人士的个人喜好来处理,这算不算滥用职权。不存在的,在中国,没人敢说的。

又记起了上个赛季发生在中超的那些处罚,罚那么重,好,罚那么重也算了,只要大家都这么罚,也没什么,但是为什么,同样的犯规,最后处罚会差那么多。

马克思主义告诉我们,事情总是相对的。社会主义国家在面对重大灾难的时候,国家就是好,集中全国干大事。但是在上述问题下,国家的干预就不那么招人喜欢了。最近一直在想一个词,饱暖思淫欲。之前,大家都在为填饱肚子在忙,没时间去了解其他事。现在不一样,“闲人”太多,消息又这么发达,也都在接触一些国外的那种“开放”、“民主”的精神,所以会出现一些问题。

《我的团长我的团》中龙文章说:英国人死于傲慢与偏见,中国人死于听天由命和漫不经心。

我只是想要宪法赋予我的那个世界。

懒加载 fragment

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
public abstract class BaseLazyFragment extends Fragment {

protected View mRootView;
protected Context mContext;
protected boolean isVisible;
private boolean isPrepared;
private boolean isFirst = true;

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
isPrepared = true;
initPrepare();
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
lazyLoad();
} else {
isVisible = false;
onInvisible();
}
}

@Override
public void onResume() {
super.onResume();
if (getUserVisibleHint()) {
setUserVisibleHint(true);
}
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (mRootView == null) {
mRootView = initView(inflater, container, savedInstanceState);
}

return mRootView;
}

//--------------------------------method---------------------------//

/**
* 懒加载
*/
protected void lazyLoad() {
if (!isPrepared || !isVisible || !isFirst) {
return;
}
initData();
isFirst = false;
}

//--------------------------abstract method------------------------//

/**
* 在onActivityCreated中调用的方法,可以用来进行初始化操作。
*/
protected abstract void initPrepare();

/**
* fragment被设置为不可见时调用
*/
protected abstract void onInvisible();

/**
* 这里获取数据,刷新界面
*/
protected abstract void initData();

/**
* 初始化布局,请不要把耗时操作放在这个方法里,这个方法用来提供一个
* 基本的布局而非一个完整的布局,以免ViewPager预加载消耗大量的资源。
*/
protected abstract View initView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState);

}

多层嵌套后的 Fragment 懒加载实现

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

/**
* @author wangshijia
* @date 2018/2/2
* Fragment 第一次可见状态应该在哪里通知用户 在 onResume 以后?
*/
public abstract class LazyLoadBaseFragment extends BaseLifeCircleFragment {

protected View rootView = null;


private boolean mIsFirstVisible = true;

private boolean isViewCreated = false;

private boolean currentVisibleState = false;

@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);

if (rootView == null) {
rootView = inflater.inflate(getLayoutRes(), container, false);
}
initView(rootView);
return rootView;
}


@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// 对于默认 tab 和 间隔 checked tab 需要等到 isViewCreated = true 后才可以通过此通知用户可见
// 这种情况下第一次可见不是在这里通知 因为 isViewCreated = false 成立,等从别的界面回到这里后会使用 onFragmentResume 通知可见
// 对于非默认 tab mIsFirstVisible = true 会一直保持到选择则这个 tab 的时候,因为在 onActivityCreated 会返回 false
if (isViewCreated) {
if (isVisibleToUser && !currentVisibleState) {
dispatchUserVisibleHint(true);
} else if (!isVisibleToUser && currentVisibleState) {
dispatchUserVisibleHint(false);
}
}
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

isViewCreated = true;
// !isHidden() 默认为 true 在调用 hide show 的时候可以使用
if (!isHidden() && getUserVisibleHint()) {
dispatchUserVisibleHint(true);
}

}

@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
LogUtils.e(getClass().getSimpleName() + " onHiddenChanged dispatchChildVisibleState hidden " + hidden);

if (hidden) {
dispatchUserVisibleHint(false);
} else {
dispatchUserVisibleHint(true);
}
}

@Override
public void onResume() {
super.onResume();
if (!mIsFirstVisible) {
if (!isHidden() && !currentVisibleState && getUserVisibleHint()) {
dispatchUserVisibleHint(true);
}
}
}

@Override
public void onPause() {
super.onPause();
// 当前 Fragment 包含子 Fragment 的时候 dispatchUserVisibleHint 内部本身就会通知子 Fragment 不可见
// 子 fragment 走到这里的时候自身又会调用一遍 ?
if (currentVisibleState && getUserVisibleHint()) {
dispatchUserVisibleHint(false);
}
}


/**
* 统一处理 显示隐藏
*
* @param visible
*/
private void dispatchUserVisibleHint(boolean visible) {
//当前 Fragment 是 child 时候 作为缓存 Fragment 的子 fragment getUserVisibleHint = true
//但当父 fragment 不可见所以 currentVisibleState = false 直接 return 掉
// 这里限制则可以限制多层嵌套的时候子 Fragment 的分发
if (visible && isParentInvisible()) return;

//此处是对子 Fragment 不可见的限制,因为 子 Fragment 先于父 Fragment回调本方法 currentVisibleState 置位 false
// 当父 dispatchChildVisibleState 的时候第二次回调本方法 visible = false 所以此处 visible 将直接返回
if (currentVisibleState == visible) {
return;
}

currentVisibleState = visible;

if (visible) {
if (mIsFirstVisible) {
mIsFirstVisible = false;
onFragmentFirstVisible();
}
onFragmentResume();
dispatchChildVisibleState(true);
} else {
dispatchChildVisibleState(false);
onFragmentPause();
}
}

/**
* 用于分发可见时间的时候父获取 fragment 是否隐藏
*
* @return true fragment 不可见, false 父 fragment 可见
*/
private boolean isParentInvisible() {
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof LazyLoadBaseFragment ) {
LazyLoadBaseFragment fragment = (LazyLoadBaseFragment) parentFragment;
return !fragment.isSupportVisible();
}else {
return false;
}
}

private boolean isSupportVisible() {
return currentVisibleState;
}

/**
* 当前 Fragment 是 child 时候 作为缓存 Fragment 的子 fragment 的唯一或者嵌套 VP 的第一 fragment 时 getUserVisibleHint = true
* 但是由于父 Fragment 还进入可见状态所以自身也是不可见的, 这个方法可以存在是因为庆幸的是 父 fragment 的生命周期回调总是先于子 Fragment
* 所以在父 fragment 设置完成当前不可见状态后,需要通知子 Fragment 我不可见,你也不可见,
* <p>
* 因为 dispatchUserVisibleHint 中判断了 isParentInvisible 所以当 子 fragment 走到了 onActivityCreated 的时候直接 return 掉了
* <p>
* 当真正的外部 Fragment 可见的时候,走 setVisibleHint (VP 中)或者 onActivityCreated (hide show) 的时候
* 从对应的生命周期入口调用 dispatchChildVisibleState 通知子 Fragment 可见状态
*
* @param visible
*/
private void dispatchChildVisibleState(boolean visible) {
FragmentManager childFragmentManager = getChildFragmentManager();
List<Fragment> fragments = childFragmentManager.getFragments();
if (!fragments.isEmpty()) {
for (Fragment child : fragments) {
if (child instanceof LazyLoadBaseFragment && !child.isHidden() && child.getUserVisibleHint()) {
((LazyLoadBaseFragment) child).dispatchUserVisibleHint(visible);
}
}
}
}

public void onFragmentFirstVisible() {
LogUtils.e(getClass().getSimpleName() + " 对用户第一次可见");

}

public void onFragmentResume() {
LogUtils.e(getClass().getSimpleName() + " 对用户可见");
}

public void onFragmentPause() {
LogUtils.e(getClass().getSimpleName() + " 对用户不可见");
}

@Override
public void onDestroyView() {
super.onDestroyView();
isViewCreated = false;
mIsFirstVisible = true;
}


/**
* 返回布局 resId
*
* @return layoutId
*/
protected abstract int getLayoutRes();


/**
* 初始化view
*
* @param rootView
*/
protected abstract void initView(View rootView);
}

对于可见状态的生命周期调用顺序,父 Fragment总是优先于子 Fragment,而对于不可见事件,内部的 Fragment 生命周期总是先于外层 Fragment。

Android 命名规范

Android 命名规范

标识符命名法

标识符命名法最要有四种:

  1. 驼峰(Camel)命名法:又称小驼峰命名法,除首单词外,其余所有单词的第一个字母大写。
  2. 帕斯卡(pascal)命名法:又称大驼峰命名法,所有单词的第一个字母大写
  3. 下划线命名法:单词与单词间用下划线做间隔。
  4. 匈牙利命名法:广泛应用于微软编程环境中,在以Pascal命名法的变量前附加小写序列说明该变量的类型。 量的取名方式为:<scope_> + <prefix_> + <qualifier> 范围前缀,类型前缀,限定词。

尽可能的用最少的字符而又能完整的表达标识符的含义。

英文缩写原则:

  1. 较短的单词可通过去掉“元音”形成缩写
  2. 较长的单词可取单词的头几个字母形成缩写
  3. 此外还有一些约定成俗的英文单词缩写

下面为常见的英文单词缩写:

名称 缩写
icon ic
color cl
divider di
selector sl
background bg
image img
password pwd
position pos
TextView tv
ImageView iv
EditText et

注意: 单词缩写原则:不要用缩写,除非该缩写是约定俗成的。

命名规范

采用反域名命名规则,全部使用小写字母。一级包名为com,二级包名为xx(可以是公司或则个人的随便),三级包名根据应用进行命名,四级包名为模块名或层级名。

包名 此包中包含
包名.activities Activity类
包名.base 自定义基类
包名.adapter Adapter类
包名.tools 公共工具方法类
包名.bean 包中包含:实体类
包名.view (或 widget) 自定义的View类等
包名.service Service服务

采用大驼峰命名法,尽量避免缩写,除非该缩写是众所周知的,比如 HTML , URL,如果类名称中包含单词缩写,则单词缩写的每个字母均应大写。

描述 例如
activity Aty或者Activity为后缀标识 WelcomeAty.或者WelcomeActivity
Adapter Adapte 为后缀标识 NewsAdp或NewsAdapter
基础类 以Base开头 BaseActivity,BaseFragment
公共方法类 Tools或Manager为后缀标识 ThreadPoolManager, LogTools
Service 以Service为后缀标识 TimeService

方法

动词或动名词,采用小驼峰命名法例如: onCreate(), run()

变量

采用小驼峰命名法。类中控件名称必须与xml布局id保持一致。

用统一的量词通过在结尾处放置一个量词,就可创建更加统一的变量,它们更容易理解,也更容易搜索。例如,请使用 strCustomerFirststrCustomerLast ,而不要使用 strFirstCustomerstrLastCustomer

常量

全部大写,采用下划线命名法.例如:MIN_WIDTH

资源文件

全部小写,采用下划线命名法,加前缀区分

名称 功能
btn_xx 按钮图片使用btn_整体效果(selector)
btn_xx_normal 按钮图片使用btn_正常情况效果
btn_xx_press 按钮图片使用btn_点击时候效果
bg_head 背景图片使用bg_功能_说明
ic_more_help 图标图片使用icon_功能_说明

资源ID

大小写规范与方法名一致,采用小驼峰命名法。命名规范为“资源控件的缩写 名”+“变量名”。注意:页面控件名称应该和控件id名保持一致

layout中的id命名

命名模式为:view缩写_模块名称_view的逻辑名称

控件 缩写
RelativeView rv
TextView tv
Button btn
ImageView iv
ProgressBar pb

注意: 如果layout文件很复杂,建议将layout分成多个模块,每个模块定义一个moduleViewHolder,其成员变量包含所属view

styles.xml

将layout中不断重现的style提炼出通用的style通用组件,放到styles.xml中

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×

keyboard_arrow_up 回到顶端