android的APT技术

转载请标明出处:http://77blogs.com/?p=199

APT 是Annotation Processing Tool 的简称。

它是注解处理器,在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成源文件和原来的源文件,将它们一起生成class文件。
简言之:APT可以根据注解,在编译时生成代码。

事实上它是javac的一个工具,命令行运行javac后便可以看到:

接下来我们就来实现一个apt的实例,类似于ButterKnife中@BindView注解,基本步骤如下:

1、定义要被处理的注解。

2、定义注解处理器(生成具体的类)。

3、调用处理器生成的代码

 

对应的,我们在工程中需要有这几个模块:

1、app。测试我们的功能

2、apt-annotation。一个Java library module,放置我们自定义注解

3、apt-processor。一个Java library module,注解处理器模块

4、apt-sdk。一个Android library module,通过反射调用apt-processor模块生成的方法,实现view的绑定。

工程目录如下:

1、在apt-annotation中自定义注解:

2、apt-processor中引入依赖,它需要依赖apt-annotation,同时还需要依赖auto-service第三方库,后面创建注解处理器的时候需要用到。

apt-processor/build.gradle文件中:

3、在pat-processor中创建注解处理器:

处理器需要继承AbstractProcessor,注意该module是 java module,如果创建的是android module的话那么就会找不到AbstractProcessor

需要注意的是代码中不能有中文,否则编译不通过,我这里为了方便注释解释加上了中文。

 

ClassCreatorFactory的代码如下,这个类负责提供需要写入新的类的代码:

先不谈apt-sdk模块,我们先来看看生成的代码是怎么样的。

在app的gradle中引入:

特别要注意的是apt-processor模块的依赖引进要用 annotationProcessor,否则编译报错

 

两个activity中:

rebuild一下便可以看到在这个目录下有我们生成的文件了。

gradle高版本出现编译后没出现文件的问题,无奈只好降低版本,我使用的版本是gradle  3.1.4 +  gralde_wrap  gradle-4.4-all.zip

点进入其中一个可以看到是这样的代码:

所以我们只要调用bindView就能够找到该view了,这也是apt-sdk要做的事情。

 

4、在apt-sdk中创建类,反射调用生成的类中的方法

5、app的gradle中引入apt-sdk,然后代码调用DataApi的方法

app的MainActivity中实现

这样就大功告成了

源码地址:https://github.com/TZHANHONG/AptAutoBindView

RxJava的flatmap

转载请标明出处:http://77blogs.com/?p=183

对 Observable 发射的数据都应用(apply)一个函数,这个函数返回一个 Observable,然后合并这些 Observables,并且发送(emit)合并的结果。

flatMap() 的原理是这样的
1. 使用传入的事件对象创建一个 Observable 对象
2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。

flatMap 和 map 操作符很相像,flatMap 发送的是合并后的 Observables,map 操作符发送的是应用函数后返回的结果集。

createObservable方法如下:

打印结果为:

01-02 16:52:36.969 30827-30827/com.status.rxjavasample D/RxJavaHelper: 1
01-02 16:52:36.969 30827-30827/com.status.rxjavasample D/RxJavaHelper: 2
01-02 16:52:36.969 30827-30827/com.status.rxjavasample D/RxJavaHelper: 3

RxJava的map

转载请标明出处http://77blogs.com/?p=175

map的作用:对Observable发射的数据都应用一个函数,然后再发射最后的结果集。最后map()方法返回一个新的Observable。

举个例子:

 

首先遍历集合中的每一个项,然后给map进行映射更改,每一项都加一,然后再转换为集合,然后再将该集合传入map中,在map中加入新的项4,最后给观察者的回调方法,输出该集合。

打印的log为:

12-23 21:46:36.691 3072-3102/com.status.rxjavasample D/RxJavaHelper: [4]

中间可以有多个map操作符。

.map(new Function<Integer, Integer>(){}中的Function里面的泛型参数,第一个是指输入的类型,第二个是指输出的类型。

再看下面这个例子:

输出的log为:

12-30 21:45:25.713 32565-32611/com.status.rxjavasample D/RxJavaHelper: 1
12-30 21:45:25.713 32565-32611/com.status.rxjavasample D/RxJavaHelper: 2
12-30 21:45:25.713 32565-32611/com.status.rxjavasample D/RxJavaHelper: 3

RxJava的concat操作符

转载请标明出处,维权必究http://77blogs.com/?p=170

使用场景一:

现在要执行两个任务:

1、输出字符串0

2、输出字符串1

我们就可以使用concat来实现多个数据源。

1、输出字符串0的数据源:

2、输出字符串1的数据源:

3、接收多个数据源:

可以看到Log:

12-23 20:23:48.771 23643-23643/com.status.rxjavasample D/RxJavaHelper: 0
12-23 20:23:48.771 23643-23643/com.status.rxjavasample D/RxJavaHelper: 1

两个字符串都输出了,而且是有序的。

使用场景二、

获取数据,如果从本地缓存中获取得到数据,那么便不从网络获取,否则从网络获取。

我们将上面的1,2两个步骤分别当成从本地缓存获取数据和从网络缓存中获取数据,那么我们需要改变上面的3步骤。

唯一不同的是加上.firstElement()。

输出的log为:

12-23 20:29:11.731 24458-24458/com.status.rxjavasample D/RxJavaHelper: 0

firstElement操作符:按照顺序依次遍历被观察者中事件,事件不为空,则停止遍历。

RxJava基本使用

转载请标明出处:http://77blogs.com/?p=162

RxJava究竟是啥,从根本上来讲,它就是一个实现异步操作的库,并且能够使代码非常简洁。它的异步是使用观察者模式来实现的。

关于观察者模式的介绍,可以看我这一篇文章:

https://www.cnblogs.com/tangZH/p/11175120.html 

这里我主要讲RxJava的一些基本用法,基本案例,原理的话暂时不深究:

一、自己构造事件

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

onNext():方法用来发送事件。

下面看看其他两个方法:

  • onCompleted(): 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。
  • onError(): 事件队列异常。在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。
  • 在一个正确运行的事件序列中, onCompleted() 和 onError() 有且只有一个,并且是事件序列中的最后一个。需要注意的是,onCompleted() 和 onError() 二者也是互斥的,即在队列中调用了其中一个,就不应该再调用另一个。

讲一下我们上面的例子,上面这个例子是采用简洁的链式调用来写的:

首先使用 create() 方法来创建一个 Observable ,并为它定义事件触发规则,然后通过emitter.onNext(i)传递出来,.subscribeOn(Schedulers.io())便是指定该事件产生的所在的线程为子线程,.observeOn(AndroidSchedulers.mainThread())指定观察者执行的线程为主线程。这时候为止返回的对象为Observable对象。

然后该Observable对象subscribe绑定观察者(也就是观察者进行订阅),里面有接收被观察者发出来的事件,有一个成功的方法,和一个失败的方法,这样就实现了由被观察者向观察传递事件。

二、对集合里的数据进行变换

且看,我们需要对某个集合里面的数据一一进行变换,然后发送出来执行其他操作。

上面便是对集合里面的每一项进行加一操作,然后再转换为String类型,然后toList(),组合成集合发送出来,最后在观察者方法中打印出每一项。

三、合并执行

定义两个被观察者,各自产生事件,然后合并在一起,发送给一个观察者。

首先定义我们上面第一个例子的被观察者,用于发送一个数字:

其次再定义我们上面第二个例子的被观察者:

最后将这两个被观察者的事件合并起来发送给一个观察者:

zip方法,顾名思义,有点类似与于打包的意思。

o为被观察者1返回的结果,o2为被观察2返回的结果,将这两个结果一起处理后发送给观察者。打印出来。

现在先介绍这几个,找个时间再整理一些其他的用法以及原理实现。

Android签名机制

装载请标明出处,维权必究:http://77blogs.com/?p=274

首先我们在as中双击apk,出现apk的分析界面,然后选中保存签名相关文件的文件夹:

一:签名文件:

1、MANIFEST.MF:保存了所有其他文件的SHA-1并base64编码后的值

2、CERT.SF

SHA1-Digest-Manifest的值,这个值就是MANIFEST.MF文件的SHA-1并base64编码后的值。

后面几项的值是对MANIFEST.MF文件中的每项再次SHA1并base64编码后的值。

将上一个文件的某一项取出,比如:

Name: assets/BookCategoryConfig

SHA1-Digest: u5YLUiucukHRhO/xAqnzbnCb6cU=

加两个\r\n,保存文件,再SHA1并base64编码便可得到。

3、CERT.RSA:

包含了公钥信息和发布机构信息。它把之前生成的 CERT.SF文件, 用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。CERT.RSA是一个满足PKCS7格式的文件。

 

Name”属性,其值就是该文件在apk包中的路径。

 

二、签名过程

CERT.RSA文件生成:

它会把前面生成的 CERT.SF文件用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。CERT.RSA是一个满足PKCS7格式的文件。

 

三、APK安装校验过程

1、通过在CERT.RSA文件中记录的签名信息,验证了CERT.SF没有被篡改过

RSA是一种非对称加密算法。用私钥通过RSA算法对摘要信息进行加密。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息进行对比,如果相符,则表明内容没有被异常修改。

2、通过CERT.SF文件中记录的摘要值,验证了MANIFEST.MF没有被修改过

3、apk内文件的摘要值要与MANIFEST.MF文件中记录的一致

这里简单介绍下SHA1数字签名。简单地说,它就是一种安全哈希算法,类似于MD5算法。它把任意长度的输入,通过散列算法变成固定长度的输出(这里我们称作“摘要信息”)。你不能仅通过这个摘要信息复原原来的信息。另外,它保证不同信息的摘要信息彼此不同。因此,如果你改变了apk包中的文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是程序就不能成功安装。

 

1、 Android签名机制其实是对APK包完整性和发布机构唯一性的一种校验机制。

2、 Android签名机制不能阻止APK包被修改,但修改后的再签名无法与原先的签名保持一致。(拥有私钥的情况除外)。

3、 APK包加密的公钥就打包在APK包内,且不同的私钥对应不同的公钥。换句话言之,不同的私钥签名的APK公钥也必不相同。所以我们可以根据公钥的对比,来判断私钥是否一致。

软键盘无法把内容顶出屏幕外

转载请标明出处,维权必究:http://77blogs.com/?p=275

 

继上次这一篇后:https://www.cnblogs.com/tangZH/p/12013685.html

我继续探讨,这次的场景和上一次的场景是一样的,在一个布局中,根布局是相对布局,需要被软键盘顶上去的部分为线性布局。线性布局最下方为一个Editext,后来发现,当线性布局的内容过多的时候,线性布局没法被顶出屏幕外,到达顶端就停止了,导致下方的EdiText被软键盘遮挡住。

 

我在根布局最外层套上一个ScrollView,这样一来发现下方的Editext还是会被遮挡住,但是不同的是,这时候你可以去手动将整个布局往上滑动,可我们需要的是顶上去之后自动露出来全部,而不是靠用户手动去滑,于是乎我想到了下面这种方法:

监听view布局发现变化(也就是软键盘出现或者消失的时候)的时候就将scrollView滑动到最底下,这样就能够使得需要显示的内容不被软键盘遮挡。

android只设置部分控件随着软键盘的出现而腾出空间

转载请标明出处:http://77blogs.com/?p=276

 

在项目过程中,出现了一个需求,软键盘要顶起部分控件,而另一部分控件不动。

关于这种需求,我们需要明确布局方式:

1、线性布局是行不通的,即使被顶上去也是全部被顶上去,因为线性布局中里面的控件都是线性排列的,那么我们就用相对布局这种方式。

2、相对布局这种方式中,需要被顶上去的那一部分需要用一个父布局包裹起来,并且与不需要顶起来的那一部分不能有依赖关系,比如layout_above这类的,否则一个位置改变,另一个也会跟着改变。

项目中需要被顶起来的那一部分使用了:android:layout_alignParentBottom=”true”,置于底部。

 

其余的:

布局这样子之后,还要在manifests文件里面配置android:windowSoftInputMode=”adjustResize”

 

然而会发现还是没有被顶起来,其实还差一个,在需要被顶起来的那一个父布局里面加上android:fitsSystemWindows=”true”

 

这样又出现了另一个问题:当我们使用沉浸式状态栏的时候,设置android:fitsSystemWindows=”true”会导致该父布局上面多出一块空白,据说这块空白的高度就是状态栏的高度。

 

最后发现可以用下面的方法解决:

需要被顶起来的一个父布局采用自定义的布局,然后重写相应的方法:

这样便解决了。

参考:https://blog.csdn.net/dbmonkey/article/details/84966318

 

Android自定义ViewPager:水平滑动弹性效果,侧滑刷新加载的ViewPager

转载于:https://blog.csdn.net/Excellence1981/article/details/70807873

 

 

去除TextView设置lineSpacingExtra后,最后一行多出的空白

转载请标明出处:http://77blogs.com/?p=278

有些手机中,给TextView设置lineSpacingExtra后会出现最后一行的文字也出现lineSpacingExtra,不是某些版本才会,这跟机型有关。

可以用下面这种方法解决: