Okhttp: 在网络框架中我使用的是谷歌官方支持的okhttp网络请求框架。相对于原生、httpurlconnect和其他框架,okhttp使用简单、方便、支持网络请求复用、封装了对网络请求过程进行的处理,效率高。
Okhttp通过构建者模式可以构建拦截器、线程分发器、代理、和Socket以及请求体,通过构者构建出okhttpClicent对象,再通过newcall方法获得RealCall请求对象。通过RealCall发起同步或异步请求,而决定是异步还是同步请求的是由线程分发类dispatcher来决定,它内部创建了三个队列,异步等待队列、异步执行队列、同步执行队列。当发起同步请求时会将请求加入到同步队列中,一次执行,所以会阻塞UI线程,需要开启子线程执行,当发起异步请求时会创建一个线程池,并且判断请求队列是否大于最大请求队列64,请求主机数是否大于5,如果大于请求添加到异步等待队列中,否则添加到异步执行队列,并执行任务。
不管是同步或异步都回执行拦截器链首先经过自定义拦截器,而自定义拦截器一般是Iterceptor或interceptor的实现类,所以会按责任链模式依次
向下执行,先经过重定向拦截器,然后是桥接拦截器,完善请求头后经过缓存拦截器,缓存拦截器使用的是策略模式strategy,通过DiskLruCache
算法来执行缓存策略,后边连接拦截器建立和服务器的连接,由服务拦截器进行网络数据的获取。在服务拦截器内部它采用了Socket进行的网络通讯,并采okio进行的流处理。
获取到数据后,按照拦截器责任链
的反向执行,当执行到缓存拦截器后,如果刚刚请求的网络数据不为空且之前有该路劲的缓存,则更新缓存内容,如果之前没有缓存,则
进行缓存,最后经过重定向拦截器时,会判断是否请求到数据,如果没有则重新请求,如果有数据将数据通过自定义拦截器返回给线程分发器。
因为回调接口是在子线程中执行的,所以更新UI的话我们需要切换回主线程,并且在读取数据时当读取一次后,它的流关闭不允许再读,对返回的数据没有做转换,需要我们手动去做。
With:当上下文对象传入的是非全局context,如果glide运行在非UI线程或api小于11,使用全局上下文,如果glide在UI线程加载图片,因为glide无法得知寄存体的生命周期, glide底层创建出一个不可见fragment,而fragment的生命周期和activity同步,所以当activity ondestory的时候,停止glide图片加载。
如果是全局上下文,那么glide加载图片跟随application的生命周期。
缓存:glide支持默认缓存,当然根据实际需求可以关闭内存缓存。本地缓存需要手动设置,总共有四个缓存类型,all:缓存资源和处理结果;source:只缓存资源;result:只缓存处理结果图;none:不进行本地缓存;缓存采用lrucache算法,本地缓存支持的默认空间为250M,使用线程池处理本地缓存,线程池的核心数量等于获得可用的处理器个数。
Load:load方法根据传入类型不同,有多个重载,每个重载方法最后都会返回一个DrawableTypeRequest 对象,他的父类DrawableRequestBuilder是一个支持链式调用的类。
Into:into是所有方法中最为复杂的,里边传入要显示图片的view,将处理后的图片设置到view上,因为涉及到UI的更新,所以底层会检查是否是主线程。传入的view在glide底层被封装成了一个target对象,target能够获取自身绑定的请求,当发现之前的请求还在的时候,会把旧的请求清除掉,绑定新的请求,这也就是为什么控件复用时不会出现图片错位的问题。设置图片之前,首先会从memorycache中读取,如果没有从磁盘读取,当然读取resource还是result是有磁盘缓存的策略决定的,获取到对应的图片后将图片设置给imageview。
特殊形状图片设置:glide3设置图片形状通过transform方法里边传入自定义view,glide4新增了apply方法直接设置常用形状。
Retrofit网络请求本质是okhttp来完成的,它仅仅是对网络请求接口的封装。Retrofit最外层通过建造者模式进行了封装,内部封装了网络请求适配器工厂、数据解析工厂、rxjava解析工厂、okclient等对象。网络请求执行器okhttpcall默认通过handler来进行线程的切换,通过手动设置rxjava解析工厂,使用rxjava scheduler进行线程的切换,首先网络请求工厂生产ok call对象,对使用平台进行确认,然后根据传入的请求路劲,将String类型的URL转为适合ok请求的httpurl类型的URL,gson转化工厂底层创建gson对象,并携带对象加入到转换工厂数组converterFactories数组,等待分配任务进行对象转换。
接口创建采用了外观模式和代理模式,首先通过外观设计模式create()方法获取封装好的请求对象,通过动态代理获取到ok的网络请求对象,通过策略模式来决定是用handler还是rxjava进行线程切换,OKhttpcall在发送请求时会进行线程的切换,所以采用了装饰模式,请求分为同步和异步,根据ok提供的线程分发器和拦截器进行请求。
代码混淆
意义:被反编译,不容易理解;优化java的字节码,使程序运行更快;减少App大小,在混淆过程中它会找出未被使用过的类和类成员并删除他们;
打开和关闭代码混淆,主要通过控制build.gradle中buildTypes的minifyEnabled值,如果是true即混淆,如果为false是不进行混淆,一般默认不混淆,如果项目上线需要混淆的话,需要手动设置为true。当设为true是会混淆所有类名和方法名以及一些文件名,但是实际开发中,很多东西是不能进行混淆的,比如说四大组件,因为在清单文件中进行了配置,一旦混淆,系统将找不到该类,包括R文件中的静态常量,因为都是被引用的,一旦混淆,引用的地方将找不到该静态常量,还有比如说c或c++通过jni调用的类、方法,混淆后,仍然会出现找不到的情况,所以这些都是不能混淆的,通过各种keep方法,避免混淆。
Log混淆
1,打开优化开关
2,在混淆文件中加入混淆代码
1.proguard-android.txt:默认混淆规则,包含一些比较常规的规则,位于SDK根目录\tools\proguard\proguard-android.txt
2.proguard-rules.pro:自己的项目需要特别定义混淆规则,它位于项目根目录下面,里面的内容需要我们自己编写
3.aapt_rules.txt:打包时混淆过程中生成的文件,如果混淆过程中遇到错误,可以在这里进行定位。文件项目根目录的:build\intermediates\proguard-rules\release\aapt_rules.txt
加固
https://blog.csdn.net/qq_30304193/article/details/81778579
对抗发编译工具:通过代码配置,当反编译工具进行反编译时,让反编译工具崩溃
对抗模拟器
1,通过判断模拟器特有的几个文件
2,检测模拟器默认的电话号码
3,检测设备的ids是不是000000000000000
3,检测IMSI id是不是310260000000000
4,检测手机硬件信息
5,检测手机运营商家
对抗重打包:当apk被反编译后,重新打包时判断签名是否和项目的签名一致,如果不一致的话,可以执行退出程序或杀死进程的操作
多渠道打包:
原理:在menifest为每个平台或市场的apk指定一个唯一的标识符
流程:下载友盟sdk,集成到studio中,在清单文件中配置好渠道占位符和appkey,在应用的modual中的build.gralde中编写多渠道脚本。在控制台通过命令./gradlew assembleRelease自动打出多渠道的包。
关于greendao数据库升级问题
在实际开发当中,我们经常要对数据库进行升级,但GreenDAO默认的DaoMaster.DevOpenHelper在进行数据升级时,会把旧表删除,然后创建新表,并没有迁移旧数据到新表中,从而造成数据丢失,所以使用greendao我们一般不会直接使用DaoMaster.DevOpenHelper,而是自己创建一个类继承DaoMaster.DevOpenHelper
,然后重写onUpgrade方法,在这个方法中使用MigrationHelper创建一个临时表,将旧表的数据迁移到一个新表中,保证缓存数据不会丢失。当然需要用自己继承
DaoMaster.DevOpenHelper的类来创建数据库,当升级时出现实体类中字段的增删改,变量类型需要使用基本数据类型的包装类,修改或新增的字段在数据库更新后默认为null。然后删除实体类中的构造方法、get、set方法,重新make project生成,最后将build.gradle中的schemaVersion增加1即可。
内存泄漏:
定义:activity销毁后,当gc回收时,发现该activity的引用被持有,导致不能回收。(内存泄漏一般不会导致崩溃,但如果项目中大量的出现内存泄漏,非常容易引发Out Of Memery)
内存泄漏的检测:在Androidstudio3.0以后,通过Android profiler(3.0以前使用Android monitor)中的memery选项,查看内存使用情况,如果检测某一个activity中是否存在内存泄漏,可以将该activity进行finish,然后手动触发GC,再对项目的包进行捕捉查看,如果gc后发现在对应包下,仍然存在改activity引用,说明该activity已经发生了泄漏,可以根据引用的对象,进行初步排查,当然不管使用studio自带的检测工具,还是使用MAT工具,只能检测出大部分的泄漏问题,或者有时候即使发现泄漏,很难确定位置,这个时候主要还是靠开发人员通过经验判断逐步排查。
导致原因及处理方式:
**1,内部类导致的内存泄漏:**因为内部类默认持有外部类的引用,所以当activity销毁的时候,如果触发gc,可能会导致该activity不能被回收。解决方法:将内部类改为静态内部类,因为静态内部类的实例对象不会和外部类的实例对象绑定。 它只能访问外部类的静态成员变量或者静态方法,而静态的生命周期和application一样长,所以不会影响activity的回收。
**2,Handler导致的内存泄漏:**首先,handler很多时候也是作为一个内部类使用,所以同上,改为静态内部类。其次,handler经常执行一些耗时的操作,如果耗时任务还没有执行完成,而这个时候activity已经销毁,当gc回收的时候发现activity仍被持有,导致不能被回收,所以一般在activity销毁的时候(也就是onDestroy的时候)将所有消息清空(removeCallbacksAndMessages)并将handler置空。
**3,单例设计模式导致的内存泄漏:**因为在使用单例的时候,经常会传入一个本类的上下文对象,而单例是静态的,生命周期和application一样长,当activity销毁的时候,改单例持有activity的引用导致其不能被回收,出现内存泄漏。解决方法:
在使用上下文的时候,传全局上下文。
4,Webview
关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。
创建时不要在xml中创建 而是在代码中创建
最终的解决方案是:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView。
5,动画以及计时器
动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。
当我们Activity销毁的时,有可能Timer还在继续等待执行TimerTask,它持有Activity的引用不能被回收,因此当我们Activity销毁的时候要立即cancel掉Timer和TimerTask,以避免发生内存泄漏。
6,Mvp(网络请求,接口回调)
上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式
使用MVP模式的主要作用,是它解决了业务逻辑和数据存取的紧耦合,使Presenter作为view和model的中间人,降低了数据和view的耦合度。所以MVP有很多有点:利于维护、易于测试、松耦合、复用性高,易于扩展。但由于presenter中经常进行一些耗时操作,例如网络请求,但是presenter持有了Activity的强引用,如果在请求结束之前,Activity被销毁,那么会导致presenter一直持有Activity的引用,使得Activity无法被回收,而发生内存泄漏。
解决方法:
通过弱引用和Activity、Fragment的生命周期来解决。
@Override
public void onDestroy() {
super.onDestroy();
mPresenter.destroy();
}
3 public void cancleTasks() {
// TODO 终止线程池ThreadPool.shutDown(),AsyncTask.cancle(),或者调用框架的取消任务api
}
7,自定义view
View中如果有线程或者动画,需要及时停止
在View的onDetachedFromWindow方法可以停止线程和动画,因为当View被remove或是包含此View的Activity退出时,就会调用View的onDetachedFromWindow方法。如果不处理的话很可能会导致内存泄漏
8,资源对象没关闭造成的内存泄漏(数据库使用完为关闭,流或游标未关闭,eventBus、广播未取消注册(Android8.0后取消静态广播)、MediaPlayer在activity销毁的时候,未调用release释放)
9,大文件的读写操作,数据库的读写操作
10,集合未清空(不会泄露,但是会消耗内存)
我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
11,布局中引用VideoView导致的内存泄漏:因为布局中加载videoview的时候,默认持有所在view的引用,当activity销毁后,可能导致该view持有activity的引用而不能回收。解决方案:1,代码中创建videoview,使用全局上下文2,重写attachbasecontext,使用contextWrapper上下文