RxJava源码解析一

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

本文基于RxJava 3.0.0

我们从最简单的开始,挑最主要的讲:

我们先看create方法:

该方法返回的是Observable对象,onAssembly方法可以占时不用理会,我们看

new ObservableCreate<T>(source):

也就是我们最终返回的是ObservableCreate对象,他继承Observable,持有ObservableOnSubscribe的引用。

 

接下来看subscribe方法:

接着:

可以看出,我们传进来的Consumer对象被放进了LambdaObserver中。接下来重点是subscribe(ls);

subscribeActual(observer);是一个抽象方法,实现它的方法有很多,从上面的分析中可以知道,调用下面方法的是ObservableCreate对象。

所以subscribeActual(observer);调用的是ObservableCreate里面的。

这个方法里面构造了一个发射器CreateEmitter,传进去的observer就是上面所说的LambdaObserver,里面持有我们传进去的Consumer对象。

observer.onSubscribe(parent);调用的是LambdaObserver里面的onSubscribe;

这个方法里面的onSubscribe.accept(this);中的onSubscribe,是我们上面的方法中的第四个参数,是一个空的Consumer,所以不用理他:

我们回过头看subscribeActual方法中的source.subscribe(parent);

source是我们之前传进去的ObservableOnSubscribe对象,source.subscribe便是调用ObservableOnSubscribe的subscribe方法,传进去的是一个发射器。

到此我们总算调用到了外面这个方法了。

(也就是说必须调用subscribe方法,才能发射事件)

我们再看e.onNext(1),在ObservableCreate的内部类CreateEmitter里面:

这里的observer我们上面已经分析过,是构造发射器的时候传进去的LambdaObserver,LambdaObserver里面持有我们传进去的Consumer对象。

 

于是observer.onNext(t)便是调用LambdaObserver的onNext(t)

onNext便是它持有的的Consumer对象,于是onNext.accept(t)便回调到了外面这一层的accept:

到此结束。

Android 跳转权限设置界面的终极方案

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

有时候APP需要获取某些权限,要让用户手动打开,那么问题来了,若是直接提醒用户出去找权限入口,然后自己打开,用户不一定找得到,因为现在的Android厂家定制的room五花八门,那么用户觉得不耐烦就有可能流失这部分用户。

所以,我们需要给用户一个入口,让用户直接在APP里面跳转到对应的权限页面。但刚刚也说了,现在的Android厂家定制的room五花八门,所以不同的机型或者android版本打开权限页面的方法就可能不一样了,我们得去适配。

 

网上查了很多资料,什么中级终极重级的方案都有,虽然这确实能够解决一些机型的适配,但还是没能给出一个通用的解决方法。

比如有人说:

跳转华为的权限界面该这样:

若是失败就直接打开默认界面:

从上面可以看出其主要的方法在与:

ComponentName comp = new ComponentName(“com.huawei.systemmanager”, “com.huawei.permissionmanager.ui.MainActivity”);

里边的参数前一个是华为权限界面的包名,华为权限界面的类名,既然知道包名与类名自然可以打开该Activity。

 

其他机型也是用这样的思想去解决,那么问题就出现了,看了网上这么多适配的方案,都是这样的思想啊,写来写去都差不多,那他们怎么就知道这些机型的权限界面的包名与类名呢?他们从来都没说,或许是我没看到,尴尬。

 

好了,所有的方法最终归回到终极方案,下面便是最终方法:

找到该机型,然后找打你想要的权限界面,可能比较难找,不过你还是要找到它,然后打开,接着通过adb命令查看当前页面的Activity,就可以看到该权限界面的包名与类名。

具体方法看链接:

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

当然,你想要适配什么机型什么android系统版本,就需要找到这样一台手机去执行这样的操作,拿到权限界面的包名与类名后就可以用上述类似的方法,在app里边直接打开权限界面,没办法,好好适配吧,少年。

解决小米手机USB安装apk时AS报错:INSTALL_FAILED_USER_RESTRICTED

今天,直接用AS在小米手机上运行安装的时候总是报错:INSTALL_FAILED_USER_RESTRICTED,于是乎,通过以下方式解决:

 

在开发者选项将USB安装打开,然后,哈,解决了。记录一下。

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

JSONObject.parseObject

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

{

“data”:{

“shop_uid”:”123″;

“id”:”123″

}

}

将上面的json字符串转换为JSONObject之后可能会出现顺序不一样,即在JSONObject中,可能是下面的顺序:

 

“id”:”123″

“shop_uid”:”123″;

 

为了保证顺序一样,可以用:

JSONObject jsonObject = JSONObject.parseObject(dataString, Feature.OrderedField);

后面加上参数:

Feature.OrderedField

android Activity runOnUiThread() 方法的使用

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

利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable).

Runnable对像就能在ui程序中被调用。

从上面的源代码中可以看出,程序首先会判断当前线程是否是UI线程,如果是就直接运行,如果不是则post,这时其实质还是使用的Handler机制来处理线程与UI通讯。

用这种方式创建ProgressDialog就比较方便,或者刷新adapter也比使用Thread+Handler方便。

如果不是在activity中创建,需要在前面加上((Activity)mContext). 。

readLine()的注意点

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

我在用socket做即时通讯的时候,读取服务器返回的信息用了BufferedReader,用起来挺方便的。

BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line ;
while ((line = br.readLine())!=null){
}
readLine()用起来很方便,每次都是返回一行。
不过该方法有许多值得注意的地方:

一、网络模式:
1、在网络上,readLine()是阻塞模式,也就是说如果readLine()读取不到数据的话,会一直阻塞,而不是返回null,所以如果你想要在while循环后执形相关操作是不可能的,因为while()里面是一个
死循环,一旦读不到数据,它又开始阻塞,因此永远也无法执形while()循环外面的操作,所以应该把操作放在while循环里面。(在我做的即时通讯里,为了能够不断获取服务器返回的消息,就是用这种方法,不断去服务器获取消息
,一旦有就返回。)

2、在while()里面判断readLine()!= null的时候要赋值给一个String,因为如果不为null,那么这时候已经读了一行。如果用while (br.readLine()!=null),那么下面没法再获取到这一行,所以应该用
while ((line = br.readLine())!=null){}

3、readLine()通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行,所以我们在发送数据的时候要再后面加上这些标志符,否则程序会阻塞。而我是直接用下面这种方法:
PrintStream ps = new PrintStream(socket.getOutputStream(), true, “UTF-8”);
ps.println()。
ps.println()已经包含换行了,所以不要用print(),若是要就要在后面加上换行符;

4、readLine()只有在数据流发生异常或者另一端被close()掉时,才会返回null值。

二、读取文件模式:
1、readLine()什么时候才会返回null呢?读取到文件等的结尾时候。(注意和网络上的是不一样的)。

如果不指定buffer大小,则readLine()使用的buffer有8192个字符。在达到buffer大小之前,只有遇到”/r”、”/n”、”/r/n”才会返回。

Android获取定位权限,获取设备所在的经纬度

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

前言:

有时候我们仅仅是想要获取设备所在的经纬度,那么直接调用Android相关的api就可以了,不需要去接入高德地图或者谷歌地图等等。

一、获取定位服务

二、获取所有可用的位置提供器

三、判断可用的位置提供器类型,是网络定位,还是GPS定位,若是都没有,那么就跳转至设置界面,提示打开网络和GPS定位服务

这里说一下两种定位的区别:

通过GPS定位,较精确,也比较耗电,而网络定位精度不高,省电。

四、获取Location

五、监视地理位置变化

这里设置3秒监听一次

六、实现地理位置变化接口

注意:

要在文件清单里面写上

我为此写了个工具类。

这里边涉及到了另一个问题,获取权限

对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装,造成了我们想要使用某个app,就要默默忍受其一些不必要的权限(比如是个app都要访问通讯录、短信等)。而在6.0以后,我们可以直接安装,当app需要我们授予不恰当的权限的时候,我们可以予以拒绝。当然你也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。

 

新的权限机制更好的保护了用户的隐私,Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录等。

 

对于Dangerous Permission,我们需要动态获取权限,那么我们怎么动态定位权限呢?

在activity中(其实我们可以把权限的获取写在一个BaseActivity中)我们可以这么做:

1、判断版本,如果6.0以下,那么便不需要获取权限。

2、若是6.0以及以上的话,那么便去判断是否已经授权,若是没有授权,那么便会去申请授权。若是已经授权,那么便直接执行我们的操作。

(判断之后若是没有授权,会弹出系统对话框询问是否同意授权,该对话框不可定制。)

3、授权回调。询问是否同意授权的时候,系统会弹出对话框,我们选择之后,会进行回调。在回调里面进行判断。

若是同意了,那么便可以直接执形我们的操作,若是不同意,那么下次依旧会弹出询问的对话框。不过这个对话框与之前比起来多了一个选项,那就是“不再提醒”(第一次询问没有这个选项)。这就难办了,若是点击了不再提醒,那么以后就不会再弹出对话框了,唉,那我们怎么判断呢?

Google给我们提供了一个方法:

该方法主要是为了给用户提供解释。

这个方法,在没弄明白之前,也是挺烦人。弄明白之后就好办了,那么,我们需要弄明白它的返回值。

(1)、当用户第一次被询问是否授权的时候,该方法返回值为false

(2)、若是用户上一次拒绝授权,但是没有点击“不再提醒“”的时候,这一次返回true。(我们可以根据这个判断,在这一次给用户一个解释,解释为什么该app需要该权限)

(3)、当用户上一次拒绝授权,并且点击了“不再提醒”的时候,那么返回false。

(4)、当用户在设置界面手动关闭了该APP的权限,那么也返回false。

 

那么,就出现了一个问题,这么多种情况都返回false,那么我们要怎么判断是哪一种情况呢?

比如第一与第三种情况。

若是我们在进行权限判断之前调用这个方法:

若是返回true,说明是第二种情况,需要给用户一个解释(我们可以给出一个对话框,点击确定后再执形检测权限的操作,即下面的的操作)。

若是返回false,那么到底是第一还是第三种情况。我们无法判断。这里我给出一种方法:在授权回调里面去检测,如下。

else可以说明已经拒绝授权。

然后调用shouldShowRequestPermissionRationale方法,若是返回false,说明

1、点击了“不再提醒”。

2、该app本身没有权限,被关闭。

那么我们就可以提醒用户去设置界面手动开启权限。

 

MAT分析android内存泄漏

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

泄漏,泄漏,漏~

内存泄漏怎么破,什么是内存泄漏?与内存溢出有什么区别?

 

内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;

 

内存泄漏不一定会引起奔溃,但是内存溢出一定会

 

Java里头有GC垃圾回收机制,他是怎么判断该不该回收呢?

Q1:某对象没有任何引用的时候才进行回收?

A:不。无法往上追溯到GCroot引用点的才回收。

 

Q2:某对象被别的对象引用就不能进行回收?

A:不。软引用,弱引用,虚引用都可以

 


哪些可以作为GCroot引用点:

  • Javastack中引用的对象
  • 方法区中静态引用指向的对象
  • 方法区中常量引用指向的对象
  • Native方法中JNI引用指向的对象
  • Thread-活着的线程

 

adb命令验证是否存在内存泄漏:

1、打开要测试的apk,然后返回退出到主界面

2、AS的Terminal中输入命令:

     adb shell dumpsys meminfo com.status.mattest -d 

com.status.mattest为包名

 

然后便可以看到内存的一些情况

拉到下面可以看到:

我们退出APK之后,对象本应该都被回收,然而这里可以看到,还有view以及Activity占用着内存,由此可以知道内存泄漏了。

 


我们点击AS上的按钮,进行分析。

运行apk后会出现该界面:

我们点击MEMORY进入内存分析界面:

这时候我们需要进行刚刚的操作,打开apk,然后返回键退出,然后点击上图中垃圾桶形状的图标进行垃圾回收,多点几次,直到内存没有什么变化了。

然后点击上图中类似于下载按钮的图标获取内存快照。

获取完之后,左边会出现下面这图,Head Dump便是获取内存快照后出现的,我们可以点击红圈中的图形进行保存

跟着出现的还有这个窗口。

我们可以通过包来分类查看自己的项目.

Shallow Heap(浅堆) 表示该对象自身占用的堆内存,不包括它引用的对象。
针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。

Retained Heap(深堆) 表示当前对象大小+当前对象可直接或间接引用到的对象的大小总和。
换句话说,Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存。

不过,释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被被当做Garbage。

从图中可以看出,出现了内存泄漏的是CustomUtils,MainActivity,MainActivity$1代表MaiActivity里面的一个方法。

 

点击MainActivity,可以看到这么对东西,眼花缭乱,我们根本不知道是哪个引起了内存泄漏。那咋办。铺垫结束,mat该上场了。

MAT

下载mat https://www.eclipse.org/mat/downloads.php

安装步骤很简单。

打开MAT后,点击File -> Open Heap Dump 打开刚刚保存的内存快照,会发现打不开。

因为我们保存的内存快照是不能直接在MAT上打开的,需要进行转化。

 

在AS的Terminal窗口输入:hprof-conv -z A.hprof B.hprof

A.hprof为刚刚保存后的快照文件,B.hprof为转换后的文件,也就是我们要在MAT上打开的文件

然后我们在MAT上将其打开。

点击Finish

点击Histogram

 可以通过包名来分类查看

 

从下图中可以看到引起内存泄漏的类,Objects那一列不为0的就是,与我们之前看到的一样。

 

MainActivity右键

选择图中的选项,意思是过滤掉虚引用,软引用,弱引用

之后可以看到引用的路径,CustomUtils里面的instance引用了MainActivity的context,导致了MainActivity内存泄漏。

打开代码看一下

MainActivity中:

MainActivity中调用了单例CustomUtils,并且把context传了进去,而我们知道instance作为静态对象,是GCroot引用点,所以activity关闭了它也没法被回收,那么最为被instance引用的Activity自然也无法被回收,所以导致了内存泄漏。

 

解决:

把单例模式里面的context换为全局application的context,也就是说单例里面的context的周期应该与进程一样,而不能够与应用一样。当我们关闭应用的时候,进程还在,并没有被杀死。

android使用giflib加载gif

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

背景不多说,反正ndk加载gif比java上加载gif好很多很多,主要体现在内存占用与cpu消耗上。使用ndk加载占用内存更小,消耗的cpu更少。

要使用ndk加载,需要用到giflib库,Android源代码里面其实也用到了这个库。

 

一、下载giflib

https://sourceforge.net/projects/giflib/

 

二、构建so库

1、把需要用到的代码拷贝进来

语法上就不多说了。

注意还需要用到log库和 jnigraphics库,一个是用来打印log,一个是用来图片解析的时候用到。

 

3、新建java类GifHandler,在里面新建本地方法,方便等会直接用快捷方式在native-lib里面生成

通过这些方法获取宽度,高度,帧数,渲染,打开文件。

 

4、native-lib代码,这里编写我们的本地代码

首先需要引进的头文件有:

<android/bitmap.h>NDK中带的头文件,解析图片要用到。

gif.h我们自己新建的头文件,等会讲

 

首先看openFile方法

这个方法主要是获取到GifFileType的指针地址。

 

GifFileType里面有gif文件的各种信息,定义在gif_lib.h里面。

 

从注释也可以看出具体的含义。

顺便说一下UserData,这个其实相当于tag,类似于可以给一个view  tag标识。

 

获取到到GifFileType之后便可以通过它获取宽度,高度,帧数,方法如下:

获取到相关信息后,我们就要进行渲染了。

先讲一下我们的原理,我们的原理是获取到信息。通过宽高构造一个bitmap,根据每一帧图片之间的间隔,拿这个bitmap去渲染,也就是通过这个延时时间循环去加载每一帧图片。

renderFrameN方法

AndroidBitmap_lockPixels(env, bitmap, &pixels)这个方法必须调用,它会锁定图片内存,同时呢,成功的话pixels会指向图片的地址。

相应的,下面就必须解除锁定:AndroidBitmap_unlockPixels(env, bitmap)

 

核心方法是这一句,当然这一句需要我们自定义头文件,也就是上面说到的gif.h

long delay_time = drawFrame(gifFileType, &info, (int *) pixels, index);

 

gif.h如下:

drawFrame方法如下,关于gif文件格式,可以自行百度,后面有空再写:


那么接下来就是主工程的调用了。

到此结束。

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

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