自己面试准备的Android知识点,很多都是简单描述下留一个印象,仅供参考
Android
Activity
- activity的四种状态:Activie(获得了焦点)、Paused(失去了焦点、可见)、Stoped(不可见)、Killed(被销毁)
- 生命周期:
- 正常情况下:onCreate( ) —> onStart( ) —> onResume( ),home键:onPause( ) —> onStop( ),再次回到页面时:onRestart( ) —> onStart( ) —> onResume( ),页面销毁时:onPause( ) —> onStop( ) —> onDestroy( )
- 异常情况:onSaveInstanceState( ) —> onRestoreInstanceState( )
- 优先级:前台(最高)、可见非前台(中等)、后台(最低)
- 横竖屏切换:android:configchange;取值一般为orientation、keyboardHidden、screenSize,会触发activity的onConfigChange( )方法
- 只走onPause( ),不走onStop( )的情况:打开一个半透明的activity
- A启动B,会先回调A的onPause( ),再回调B的onResume( )
- 启动模式
- standard:默认,多实例模式
- singleTop:栈顶复用模式,触发onNewIntent( )方法
- singleTask:栈内复用模式,触发onNewIntent( )方法
- singleInstance:单实例模式,会创建一个新的栈用来存放这个activity,且只放它
Broadcast
- 种类
- 无序广播 sendBroadcast( )
- 有序广播 sendOrderBroadcast( )
- 本地广播 需要借助LocalBroadcastManager,LocalBroadcastManager.getInstance(context),然后再sendBroadcast(还有个sync的方式)、注册、取消注册等
- 注册方式
- 静态注册 在清单文件中注册,在android 8.0中已经无法接收到大部分的广播,除了某些特定的,比如开机广播等。
- 动态注册 在onCreate( )注册,在onDestroy( )取消注册,生命周期和activity保持一致
- 静态注册,8.0以前,在APP不启动的情况下也能接收到广播
- 系统广播的原理
- 自定义一个广播接收者,并且重写onReceive( )
- 通过binder机制,向AMS中注册
- 广播发送者,通过binder向AMS发送消息
- AMS找到符合要求的BroadcastReceiver,将消息发送到BroadcastReceiver(一般是activity)的消息队列中
- 遍历消息队列,然后将消息回调给onReceive( )
- 本地广播原理
- 内部是通过Handler的方式发送消息,所以比系统广播更高效、安全
ContentProvider
- 应用间共享数据,相对于文件存储和SharedPreferences的全局可读可写,它可以只对部分数据进行操作
- getContentResolver( ).query(…),得到一个cursor游标,然后循环遍历cursor取出数据
- 自定义contenProvider,写一个类继承于ContentProvider,重写全部的方法(6个,四个增删改查、一个onCreate、一个getType)
- 四大组件都需要注册,所以需要去清单中注册,使用provider标签
Service
- 和Thread的区别:service是ui线程,不能执行耗时操作,thread是子线程,不能更新ui
- 开启service的两种方式:startService 和 bindService
- 生命周期
- 没有绑定activity,启动服务 —> onCreate( ) —> onStartCommand( ) —> onDestroy( ) —> 销毁服务
- 绑定activity,绑定服务 —> onCreate( ) —> onBind( ) —> onUnBind( ) —> onDestroy( ) —> 销毁服务
- 总结:
- 如果一个service被某个activity通过startService的方式启动,那么不管是否有activity使用bindService绑定服务还是unBindService解绑服务,service都会一直在后台运行,且调用多次startService,只会执行一次onCreate( ),onStart( )会执行多次,service只会被创建一次,所以只需要调用一次stopService,service 的生命周期和activity无关,要停止必须要调用stopService
- 如果一个service被某个activity通过bindService的方式启动,那么不管执行多少次bindService,都只执行一次onCreate方法,不会执行onStart方法,通过unBindService方法解绑服务,或者activity被finish、destroy
- 如果一个service既被startService也被bindService启动,那么调用unBindService不能解绑service,需要同时调用unBindService、stopService来停止服务
Fragment
- fragment的加载方式:静态加载(在xml中直接声明fragment的类名)动态加载(使用FragmentManager)
- 与viewPager结合使用时,用到的适配器:FragmentPagerAdapter(页面少)、FragmentStatePagerAdapter(页面多)
- 生命周期
- onAttach( ) —> onCreate( ) —> onCreateView( ) —> onActivityCreated( ) —> onStart( ) —> onResume( )
- onPause( ) —> onStop( )
- onStart( ) —> onResume( )
- onPause( ) —> onStop( ) —> onDestroyView( ) —> onDestroy( ) —> onDetach( )
- 通信
- fragment调用activity中的方法:getActivity( ) 强转为目标activity,然后直接调用activity的方法
- activity调用fragment中的方法:通过fragment的实例直接调用方法
- fragment调用fragment中的方法:结合第一种和第二种
- 显示方式
- add/remove:replace的实际上也是先remove再add
- show/hide:显示和隐藏fragment
- attach/detach:detach会将view从viewTree上移除,调用attach时会触发onCreateView重绘view,fragment.isAdd( )会返回false
- 懒加载
- 重写fragment中的setUserVisibleHint ( boolean isUserVisibleToUser ),然后根据isVisibleToUser来判断是否可见,需要注意的是,这个方法在onCreate( )之前执行,所以拉取数据如果需要更新UI就不行,view还没初始化。
- 针对上面的情况,fragment还提供了另外一个方式,可在onStart( )等生命周期方法中通过判断getUserVisibleHint( )的返回值,返回true则表示可见。
WebView
- JS注入的漏洞(解决办法)
- 添加@JavaScriptInterface注解
- webSetting.setSavePassword ( false ) 关闭密码保存
- setAllowFileAccess ( false ) 禁止加载本地文件
- 内存泄漏
- 原因:webView持有activity的实例,类似匿名内部类持有外部类的引用
- 解决:在布局文件中,写入一个viewGroup,在activity中通过addView的方式添加,在activity销毁的时候remove
- webView常见的三个类:WebSetting(设置)、WebViewClient(通知和请求事件,pageStart等)、WebChromeClient(网站相关,加载进度等)
- Chrome在线调试,需要webView开启调试debug模式
- WebView的一些证书、控制台输出日志等回调,在开发中很实用
- JS注入偶尔会失败,建议在pageFinish中注入对象,因为页面没有加载结束,JS的方法可能还没初始化完成
Binder
Handler
- 四个类:Handler、Message、Looper、MessageQueue
- 机制(简易版):创建一个Handler并且实现它的handleMessage( )方法,通过sendMessage( )等方法将消息发送到消息队列MessageQueue中,然后Looper循环的从消息队列中取消息,通过回调dispatchMessage( )将消息回调给handleMessage( )
- 源码细节:
- Hadnler中有MessageQueue、Looper实例,而MessageQueue是在Looper中初始化,所以Handler的MessageQueue实例其实是Looper中的
- Looper的初始化是在ActivityThread的main方法中,Looper.prepareMainLooper,并且这里调用了Looper.loop( )方法,开始循环取消息
- 主线程的Looper对象是不能在程序中退出的,会抛异常 throw new RuntimeException("Main thread loop unexpectedly exited"),退出主线程的循环是框架在退出应用程序的时候才调用
- Looper中的一些方法:prepare( )、loop( )、sThreadLocal.set ( new Looper (…) )
- loop( )方法,此处为一个死循环,不断的从MessageQueue中读取数据queue.next( ),如果没有数据就return,有数据则回调message.target.dispatchMessage( ),target为handler,在dispatchMessage( )方法中调用了handleMessage( )
AsyncTask
- 是什么:是一个封装了线程池和handler的异步框架
- 核心点:
- 三个参数,AsyncTask接受三个泛型参数 ( params, progress, result ),分别为参数、进度值、结果,也可全取void
- 四个方法:doInBackground(String… strings)(必须要重写)、onPrepareExcute( )(主线程,用于初始化,比如显示进度条)、onPostExcute( )(主线程,doInBackground执行完毕,更新UI等)、onProgressUpdata( )(主线程,更新进度条,需要在doInBackground调用publicProgress)
- 原理:线程池中的工作线程执行doInBackground中的异步任务,然后通过内部的handler发送消息
- 内存泄漏:静态内部类持有外部类的匿名引用,采用弱引用的方式
HandlerThread
- 是什么:本质是一个线程类,继承于thread
- 原理:在run方法中初始化了一个Looper对象,调用Looper的loop( )方法,不断的从MessageQueue中读取数据,没有消息则会阻塞;quit( )会清空所有的消息,quitSafely( )只会清空延时消息,无论调用了哪一个方法,Looper的循环就结束了。quit( )是MessageQueue的方法,作用是清空消息并且给变量置空、释放资源
IntentService
- 是什么:继承于Service,可以用来执行耗时操作,当任务执行完毕后服务会自动停止,不需要我们手动去停止,每个耗时操作会以一个队列的形式在IntentService的onHandleIntent( )回调中执行
- 和Service的区别:service运行在主线程,所以不能进行耗时操作,而IntentService则为了解决这个问题产生的
- 原理:
- 继承于Service,所以有着和Service一样的操作方式
- 在onCreate( )中,初始化了一个HandlerThread和Handler(创建一个HandlerThread,并获得其Looper对象,将Looper传入Handler创建一个Handler),借此构建了一个具有消息循环机制的后台线程
- 非常适合做一次性的后台任务,如下载一个文件,下载完成后自动销毁
View绘制机制
事件分发机制
ListView
- 优化:
- 复用convertView
- 使用viewHolder,频繁的findView会消耗内存
- 分页加载
- 图片缓存(图片框架加载)
- 滑动过程中不加载图片
动画机制
自定义View
Serializable和Parcelable
- Serializable是Java提供的一个序列化接口,需要提供一个序列化ID用于序列化和反序列化(需要大量的I/O操作,开销大,效率低,但是数据持久,适合序列化到本地的数据)
- Parcelable,Android SDK内部提供的一个序列化接口,不需要大量的I/O操作,开销小,效率高,适合内存序列化
Android各版本特性
- 5.0:MD风格,推出很多新控件如RecyclerView
- 6.0:动态权限,对于危险权限需要用户授权
- 7.0:文件访问权限,fileProvider
- 8.0:静态注册的广播不再适用;APP logo适配;允许安装未知来源应用;透明主题的Activity不能设置界面方向
- 9.0:前台service必须要申请 FOREGROUND_SERVICE 权限
- 10.0:暗黑模式;
Intent
- 显示意图:常见的就是activity的跳转,指明明确的组件名称
- 隐式意图
- 常见的是跳转到通讯录等,不指明明确的组件名称
- 组成成分:action(动作)、category(附加信息)、data(数据)、type(类型)等
- 自定义隐式意图:
- 在清单文件中注册intent-filter标签中申明action、category等
- 如何使用:在代码中使用intent启动这个自定义意图,注意setData( )和setType( )会互相清除,如果需要使用,则可以使用setDataAndType( )
- bundle和intent传参的区别
- intent.putExtra(…)实则还是调用的是bundle方式
- bundle是一个封装类,内部采用的是ArrayMap
- bundle更适合传递对象,intent适合传递单个字段
对话框
- dialog
- 是在window上addView和removeView的操作,window是个抽象概念,构造方法中创建了一个phoneWindow,windowManager提供了支持
- 在show( )方法中,有个onStart( )的空方法,可以在显示dialog前做一些操作,因此常见的一些自定义dialog样式,会在onStart( )方法中设置window的样式
- dismiss( )方法是线程安全的,当中根据Looper判断当前是否在主线程,主线程才去操作dismissDialog( ),非主线程则通过handler发送到主线程
- popupwindow
- toast
Context
- Context是一个抽象类,有两个具体的实现子类:ContextImpl、ContextWrapper
- Context数量 = Activity数量 + Service数量 + 进程数
- application、service都继承于ContextWrapper,activity继承于ContextThemeWrapper
- 不推荐用application去startActivity,因为非activity类型的context没有任务栈,service同理
- getApplication( )和getApplicationContext( )的本质是一样的,都是application实例,区别在于作用域,getApplication( )方法只能在activity、service中调用到,而getAppcationContext( )可以在其他场景调用
- 关于context的内存泄漏,主要是要避免context和引用者的生命周期不一致
系统架构与系统源码
冷启动和热启动
- 冷启动:后台没有该应用的进程,会先创建和初始化Application,再创建和初始化MainActivity等
- 热启动:后台有该应用的进程,可以在任务栈中看到,如用户按back、home等退出app。再次启动时,不会去创建Application,会去创建和初始化MainActivity等。
- 冷启动优化:
- 减少onCreate( )的工作量
- 不在application的初始化中做耗时操作,可以开启线程
- 布局优化
- APP启动白屏解决方案:由于绘制布局资源并不是在窗体绘制的第一步,所以会加载默认的背景色,可以在主题中设置默认的背景图<item name="android:background">@drawable/bg_splash</item>
性能优化
- 流畅性
- 布局优化(更简单的ViewGroup、更少的布局层次、include、viewStup、merge)
- 启动优化(异步加载、分步加载、延迟加载)
- 稳定性
- 崩溃:代码健壮性、异常情况的考虑
- ANR:合理的处理耗时操作
- 节省
- 内存泄漏
- 减少资源文件的大小,webp
- apk大小
进程间通信
AIDL
- 定义:接口定义语言
- 定义AIDL接口:
- 在main目录定义一个aidl的目录,然后写上和app相同的包名,在其中申明xxx.aidl
- 在aidl中,常见类型可以直接使用,如int、String、boolean、List等,但如果是对象,需要实现Parcelable接口
- 非常见类型的参数,需要申明传输方向,如in(输入)、out(输出)、inout(可输入输出),默认为in
- 文件名要以“I”开头(这个说法是旧的,当前版本任意文件名都可以)
- 生成java文件,build —> make project
- 使用:
- 查看生成的java文件,可以发现代码内有一个Stub的静态抽象类,继承于Binder,实现了我们定义的aidl接口
- 实现接口:实现一个MyAIDL.Stub( ){ … },这步就是返回了一个Binder对象
- 开放接口,实现一个service,并重写onBind( )方法,传入上述的Binder对象
- Activity调用bindService( )绑定连接此服务,在onServiceConnect( )回调接收IBinder
Binder
- Binder是一个类,实现了IBinder接口
- Binder通信机制
- 在Android系统中,Binder通信机制由四个部分组成,分别是Client、Server、Service Manager、Binder Driver
- 内存空间分为两部分:用户空间(Client、Server、Service Manager)、内核空间(Binder Driver)
- 在Binder机制中,由Binder驱动负责完成这个中转工作
- Client向Server发起IPC请求时,Client会先将请求数据从用户空间拷贝到内核空间
- 数据拷贝到内核空间后,Binder驱动再将数据拷贝到Server用户空间的缓存中
- Service Manager:主要提供了service的添加和查询
- Client、Server、Service Manager都处于用户空间的不同进程中
- Binder跨进程通讯的步骤
- 初始化Service Manager:应用程序启动时,Service Manager和Binder通讯,Binder驱动新建Service Manager对应的Binder实体
- 注册Server:Server向Binder发起注册请求,如果Service Manager中没有此服务,则添加此服务
- Client获取远程服务:Client向Binder传递要查询的Server名称,Binder驱动将该请求转发给Service Manager,然后找到对应的Server并反馈给Client,Client收到Server对应的Binder引用后,会创建一个当前Server对应的远程服务
- Client通过代理调用Server:Client调用远程服务,远程服务和Binder驱动通讯,因为远程服务中带有Server的Binder引用,所以轻而易举的就能找到,进而将Client的请求发送给Server
Java
异常
- 异常通常分为三类:Error、编译时异常、运行时异常
- Finally:
- 一般情况下都会执行,如果在try、catch代码块中返回了return,则finally代码块内容会先return执行
- 如果程序被终止了,JVM退出了,finally代码块内容不会执行
- Final关键字
- 修饰类:不可被继承
- 修饰变量:常量,只可赋值一次
- 修饰方法:不可被重写
设计模式
-
单例
//懒汉式 //缺点:多线程同时访问时会有问题,缺乏同步 private static SingletonDemo singletonDemo1; public static SingletonDemo getInstance1() { if (null == singletonDemo1) { singletonDemo1 = new SingletonDemo(); } return singletonDemo1; } //线程安全的懒汉式 //缺点:虽然加了同步锁,但是由于大部分情况下,是不需要考虑同步这个情况的,而这里每次请求都加了个同步锁 就造成了资源的浪费 private static SingletonDemo singletonDemo2; public static synchronized SingletonDemo getInstance2() { if (null == singletonDemo2) { singletonDemo2 = new SingletonDemo(); } return singletonDemo2; } //饿汉式 //缺点:类加载的时候就初始化了,没有起到懒加载的作用 private static SingletonDemo singletonDemo3 = new SingletonDemo(); public static SingletonDemo getInstance3(){ return singletonDemo3; } //静态内部类的形式 //推荐:实现了懒加载,同样也是线程安全的 private static class SingletonDemoInstance{ private static SingletonDemo INSTANCE = new SingletonDemo(); } public static SingletonDemo getInstance4(){ return SingletonDemoInstance.INSTANCE; } //枚举 //推荐:自由实例化、单实例、线程安全 enum SingleDemo{ INSTANCE; public void doSomething(){ // TODO: 2019-05-08 do something } } //双重验证 //情景分析: //线程A进入方法,发现实例没有初始化,进入代码块,同时被锁定 //线程B进入方法,发现实例没有初始化,进入代码块,发现已经被线程A锁定,此时阻塞 //线程A执行同步方法,发现实例没有初始化,则初始化实例,跳出代码块,return //线程B执行同步方法,发现实例已经被初始化,跳出代码块,return,此时返回的是线程A中初始化的实例 //逻辑上实现了线程安全,且实现了懒加载 private static SingletonDemo singletonDemo5; public static SingletonDemo getInstance5() { if (null == singletonDemo5) { synchronized (SingletonDemo.class) { if (null == singletonDemo5) { singletonDemo5 = new SingletonDemo(); } } } return singletonDemo5; } //volatile关键字,会强制将修改的值写入内存,线程A修改了变量值,线程B/C/D都会立即读取到修改后的值 private volatile SingletonDemo getSingletonDemo6;
Builder模式
-
适配器模式
- 现有类、目标类、目标接口
- 适用场景:现有类实现了某些功能,但是在目标场景下和目标接口定义不一致,则可以通过一个适配器类的方式,复用现有类的代码
- 优点
- 通过适配器,客户端可以调用同一接口,逻辑上更透明
- 解决了现有类和复用场景要求不一致的问题
- 将目标类和现有类解耦,引入一个适配器类,复用现有类的代码
- 实现方式
- 继承现有类,并且实现目标接口,调用父类的现有方法
- 实现目标接口,但是传入现有类,通过现有类的代理方式,调用现有的方法
//已存在的,具有特殊功能,但不符合我们既有标准接口的类 static class Adaptee { void specificRequest() { System.out.println("被适配器类,具有特殊功能"); } } //目标接口 interface Target { void request(); } //具体的目标类,只提供普通功能 static class ConcreteTarget implements Target { @Override public void request() { System.out.println("普通类,具有普通功能"); } } /** * 继承了被适配器类,同时实现了接口 */ static class Adapter extends Adaptee implements Target { @Override public void request() { super.specificRequest(); } } /** * 通过委托的方式 */ static class Adapter1 implements Target { Adaptee adaptee; Adapter1(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void request() { this.adaptee.specificRequest(); } } /** * 适用场景: * 旧的代码已经实现了某些功能,但是此时我们想以另外一个接口的形式表示,且不改动原有的代码 */ public static void main(String[] args) { Target concreteTarget = new ConcreteTarget(); concreteTarget.request(); Target adapter = new Adapter(); adapter.request(); Target adapter1 = new Adapter1(new Adaptee()); adapter1.request(); }
-
装饰模式
- 情景:动态的给一个对象添加额外的职责,比直接继承的子类更灵活
- 优点:通过使用不同的具体装饰类的互相组合,会创建出更多不同行为的组合
- 缺点:会产生比继承关系更多的对象,查错比较难,而且命名上都比较相似,不易分辨
-
外观设计模式
- 概念:提供了一个高层接口,便于调用
- 场景:在不断的迭代过程中,产生了很多小的类,这使得系统更具复用性、灵活。但是这也给那些不需要定制化的系统的使用带来了麻烦,所以可以提供一个高层的类,当然,也可以绕过这个高层类
-
观察者模式
概念:被观察者通过注册观察者,当被观察者发生变化时,通知观察者
-
实现步骤:
- 抽象观察者
- 抽象被观察者
- 实现具体的观察者
- 实现具体的被观察者
-
情景:当一方面依赖于另一方面时
//抽象观察者 interface Watcher { void update(); } //抽象被观察者 interface Watched { void addWatcher(Watcher watcher); void removeWatcher(Watcher watcher); void notifyWatcher(); } //具体的观察者 public static class Police implements Watcher { @Override public void update() { System.out.println("我是警察"); } } public static class Thief implements Watcher { @Override public void update() { System.out.println("我是小偷"); } } //具体的被观察者 public static class BossMoney implements Watched { private ArrayList<Watcher> watchers = new ArrayList<>(); @Override public void addWatcher(Watcher watcher) { watchers.add(watcher); } @Override public void removeWatcher(Watcher watcher) { watchers.remove(watcher); } @Override public void notifyWatcher() { for (Watcher watcher : watchers) { System.out.println("收到通知 className: " + watcher.getClass().getSimpleName()); watcher.update(); } } } public static void main(String[] args) { BossMoney bossMoney = new BossMoney(); Police police = new Police(); Thief thief = new Thief(); bossMoney.addWatcher(police); bossMoney.addWatcher(thief); bossMoney.notifyWatcher(); }
线程、多线程、线程池
数据结构和算法
链表
队列
栈
堆
排序算法
查找算法
树
源码解析
OKHttp v3.11.0
EventBus v3.1.1
- 使用
- 注册订阅者(register)、一般在页面销毁时注销订阅者(unregister)
- 声明订阅方法(@Subscribe)
- 发送事件(post、需要定义一个实体类)
- 注解@Subscribe解析:包含ThreadMode(一个枚举类、当前线程类型)、sticky(粘性事件)、priority(优先级)
- ThreadMode的类型:POSTING(运行在发送事件线程)、MAIN(UI线程)、BACKGROUND(后台线程)、ASYNC(订阅方法和发送事件始终不在一个线程,每次都会使用新的线程来运行),默认POSTING
- sticky 粘性事件:只会接收到最近一次发送的粘性事件,之前的接收不到
- 创建
- EventBus.getDefault( ):getDefault( )是一个获取实例的方法(单例,而且是双重验证的方式,保证在不同线程中也只有一个实例)
- 也可以通过EventBus.builder( )的形式创建自定义的EventBus,这里的建造者是EventBusBuilder,使用了建造者模式
- 注册
- 创建的时候,初始化了SubscriberMethodFinder对象 —> findSubscriberMethods( ) —> findUsingInfo( ) —> findUsingReflectionInSingleClass( ) —> checkAdd( ) —> checkAddWithMethodSignature( ) —> subscriberMethods.add( ) 至此获得了符合条件的SubsciberMethod
- findUsingReflectionInSingleClass( ):通过反射的方式,遍历类中所有的方法,找到符合要求的方法;比如判断了修饰符是否是Public、参数个数是否等于1、有没有Subscribe注解
- 总结:注册的过程就是把订阅方法和事件绑定起来,放入一个Map中
- 注销
- 根据当前订阅者获取它所有的事件,然后遍历这些事件,调用unsubscribeByEventType( ),传入订阅者和事件,解除两者之间的关系
- 发送事件
- 获取postingState,当中保存了线程信息、订阅者、事件等 —> 将事件添加到队列中 —> 遍历这个队列,postSingleEvent( ) —> postSingleEventForEventType( ) —> postToSubscription( ) —> 判断线程类型是否和当前一致,是的话调用invokeSubscriber( ),否则调用enqueue( ),通过handler或者runnable切换线程使得和ThreadMode一致
- invokeSubscriber( ),通过反射的方式调用订阅方法
- 粘性事件的发送和接收分析
- 通过postSticky( )发送消息
- EventBus不知道当前订阅者对应了哪个粘性事件,所以需要全部遍历一次,然后找到匹配的粘性事件后调用postSingleEventForEventType( ),回到了postToSubscription( )方法中判断当前ThreadMode的方法中
- postToSubscription( ),有三个重要的Poster,分别是mainThreadPoster、backgroundPoster、asyncPoster,mainTreadPoster类型是HandlerPoster继承于Handler,实现了Poster接口,通过handler的方式将事件发送到主线程;backgroundPoster和asyncPoster大致上都差不多,实现了Runnable接口,区别在于backgroundPoster会判断当前线程是否在运行,而asyncPoster不会判断,每次都使用一个新线程
- 总结:从整个EventBus中可以看出,事件作为被观察者,订阅方法是观察者,当事件发出或者发生变更时,订阅者都会立马收到通知。
Glide 4.x 缓存原理
- 简介:Glide缓存分成了两个模块,一个是内存缓存,一个是磁盘缓存;内存缓存是为了避免应用重复的将图片数据加载到内存中,磁盘缓存是为了避免应用重复的从网络或其他地方下载图片
- 使用:Glide默认开启内存缓存和磁盘缓存,可以通过配置去自定义关闭,或者定义缓存类型,如只缓存原始图,或者只缓存变换后的图
- 缓存key
- Glide的缓存key生成比较复杂,在3.x版本有数10个字段,4.x版本由8个字段组成,比较明显的就是3.x的版本用到了id字段(图片url),4.x使用的是Model;决定key生成的字段有很多,这里比较明显的是width和height,也就是说,宽高不一样,生成的key相应的也不一样
- 内存缓存
- 内存缓存用到了弱引用和LruCache来制定缓存策略
- 缓存的读取:通过key从缓存中读取,并且将缓存移除,然后将缓存加入到一个弱引用的HashMap当中,如果没有读取到缓存,则再去下载图片
- 缓存的写入:图片加载完成后,会通过handler切回到主线程中,然后在后续的方法中将资源put到弱引用的HashMap中,同时还有另外一个操作,通过acquire( )和release( )的变量进行计数,++或者--
- 总结:Glide将正在使用的图片加入到弱引用当中,不常用的图片加入到LruCache中
- 磁盘缓存
- Glide会优先读取内存缓存,在读取不到的时候,再去读取磁盘缓存
- 读取:通过key从缓存中读取缓存文件,如果文件不为null,则解码后返回
- 写入:图片加载完成后,判断是否允许写入磁盘,允许的话将图片写入磁盘缓存