Android面试知识点、知识体系

自己面试准备的Android知识点,很多都是简单描述下留一个印象,仅供参考

Android

Activity

  1. activity的四种状态:Activie(获得了焦点)、Paused(失去了焦点、可见)、Stoped(不可见)、Killed(被销毁)
  2. 生命周期:
    1. 正常情况下:onCreate( ) —> onStart( ) —> onResume( ),home键:onPause( ) —> onStop( ),再次回到页面时:onRestart( ) —> onStart( ) —> onResume( ),页面销毁时:onPause( ) —> onStop( ) —> onDestroy( )
    2. 异常情况:onSaveInstanceState( ) —> onRestoreInstanceState( )
    3. 优先级:前台(最高)、可见非前台(中等)、后台(最低)
    4. 横竖屏切换:android:configchange;取值一般为orientation、keyboardHidden、screenSize,会触发activity的onConfigChange( )方法
    5. 只走onPause( ),不走onStop( )的情况:打开一个半透明的activity
    6. A启动B,会先回调A的onPause( ),再回调B的onResume( )
  3. 启动模式
    1. standard:默认,多实例模式
    2. singleTop:栈顶复用模式,触发onNewIntent( )方法
    3. singleTask:栈内复用模式,触发onNewIntent( )方法
    4. singleInstance:单实例模式,会创建一个新的栈用来存放这个activity,且只放它

Broadcast

  1. 种类
    1. 无序广播 sendBroadcast( )
    2. 有序广播 sendOrderBroadcast( )
    3. 本地广播 需要借助LocalBroadcastManager,LocalBroadcastManager.getInstance(context),然后再sendBroadcast(还有个sync的方式)、注册、取消注册等
  2. 注册方式
    1. 静态注册 在清单文件中注册,在android 8.0中已经无法接收到大部分的广播,除了某些特定的,比如开机广播等。
    2. 动态注册 在onCreate( )注册,在onDestroy( )取消注册,生命周期和activity保持一致
    3. 静态注册,8.0以前,在APP不启动的情况下也能接收到广播
  3. 系统广播的原理
    1. 自定义一个广播接收者,并且重写onReceive( )
    2. 通过binder机制,向AMS中注册
    3. 广播发送者,通过binder向AMS发送消息
    4. AMS找到符合要求的BroadcastReceiver,将消息发送到BroadcastReceiver(一般是activity)的消息队列中
    5. 遍历消息队列,然后将消息回调给onReceive( )
  4. 本地广播原理
    1. 内部是通过Handler的方式发送消息,所以比系统广播更高效、安全

ContentProvider

  1. 应用间共享数据,相对于文件存储和SharedPreferences的全局可读可写,它可以只对部分数据进行操作
  2. getContentResolver( ).query(…),得到一个cursor游标,然后循环遍历cursor取出数据
  3. 自定义contenProvider,写一个类继承于ContentProvider,重写全部的方法(6个,四个增删改查、一个onCreate、一个getType)
  4. 四大组件都需要注册,所以需要去清单中注册,使用provider标签

Service

  1. 和Thread的区别:service是ui线程,不能执行耗时操作,thread是子线程,不能更新ui
  2. 开启service的两种方式:startService 和 bindService
  3. 生命周期
    1. 没有绑定activity,启动服务 —> onCreate( ) —> onStartCommand( ) —> onDestroy( ) —> 销毁服务
    2. 绑定activity,绑定服务 —> onCreate( ) —> onBind( ) —> onUnBind( ) —> onDestroy( ) —> 销毁服务
  4. 总结:
    1. 如果一个service被某个activity通过startService的方式启动,那么不管是否有activity使用bindService绑定服务还是unBindService解绑服务,service都会一直在后台运行,且调用多次startService,只会执行一次onCreate( ),onStart( )会执行多次,service只会被创建一次,所以只需要调用一次stopService,service 的生命周期和activity无关,要停止必须要调用stopService
    2. 如果一个service被某个activity通过bindService的方式启动,那么不管执行多少次bindService,都只执行一次onCreate方法,不会执行onStart方法,通过unBindService方法解绑服务,或者activity被finish、destroy
    3. 如果一个service既被startService也被bindService启动,那么调用unBindService不能解绑service,需要同时调用unBindService、stopService来停止服务

Fragment

  1. fragment的加载方式:静态加载(在xml中直接声明fragment的类名)动态加载(使用FragmentManager)
  2. 与viewPager结合使用时,用到的适配器:FragmentPagerAdapter(页面少)、FragmentStatePagerAdapter(页面多)
  3. 生命周期
    1. onAttach( ) —> onCreate( ) —> onCreateView( ) —> onActivityCreated( ) —> onStart( ) —> onResume( )
    2. onPause( ) —> onStop( )
    3. onStart( ) —> onResume( )
    4. onPause( ) —> onStop( ) —> onDestroyView( ) —> onDestroy( ) —> onDetach( )
  4. 通信
    1. fragment调用activity中的方法:getActivity( ) 强转为目标activity,然后直接调用activity的方法
    2. activity调用fragment中的方法:通过fragment的实例直接调用方法
    3. fragment调用fragment中的方法:结合第一种和第二种
  5. 显示方式
    1. add/remove:replace的实际上也是先remove再add
    2. show/hide:显示和隐藏fragment
    3. attach/detach:detach会将view从viewTree上移除,调用attach时会触发onCreateView重绘view,fragment.isAdd( )会返回false
  6. 懒加载
    1. 重写fragment中的setUserVisibleHint ( boolean isUserVisibleToUser ),然后根据isVisibleToUser来判断是否可见,需要注意的是,这个方法在onCreate( )之前执行,所以拉取数据如果需要更新UI就不行,view还没初始化。
    2. 针对上面的情况,fragment还提供了另外一个方式,可在onStart( )等生命周期方法中通过判断getUserVisibleHint( )的返回值,返回true则表示可见。

WebView

  1. JS注入的漏洞(解决办法)
    1. 添加@JavaScriptInterface注解
    2. webSetting.setSavePassword ( false ) 关闭密码保存
    3. setAllowFileAccess ( false ) 禁止加载本地文件
  2. 内存泄漏
    1. 原因:webView持有activity的实例,类似匿名内部类持有外部类的引用
    2. 解决:在布局文件中,写入一个viewGroup,在activity中通过addView的方式添加,在activity销毁的时候remove
  3. webView常见的三个类:WebSetting(设置)、WebViewClient(通知和请求事件,pageStart等)、WebChromeClient(网站相关,加载进度等)
  4. Chrome在线调试,需要webView开启调试debug模式
  5. WebView的一些证书、控制台输出日志等回调,在开发中很实用
  6. JS注入偶尔会失败,建议在pageFinish中注入对象,因为页面没有加载结束,JS的方法可能还没初始化完成

Binder

Handler

  1. 四个类:Handler、Message、Looper、MessageQueue
  2. 机制(简易版):创建一个Handler并且实现它的handleMessage( )方法,通过sendMessage( )等方法将消息发送到消息队列MessageQueue中,然后Looper循环的从消息队列中取消息,通过回调dispatchMessage( )将消息回调给handleMessage( )
  3. 源码细节:
    1. Hadnler中有MessageQueue、Looper实例,而MessageQueue是在Looper中初始化,所以Handler的MessageQueue实例其实是Looper中的
    2. Looper的初始化是在ActivityThread的main方法中,Looper.prepareMainLooper,并且这里调用了Looper.loop( )方法,开始循环取消息
    3. 主线程的Looper对象是不能在程序中退出的,会抛异常 throw new RuntimeException("Main thread loop unexpectedly exited"),退出主线程的循环是框架在退出应用程序的时候才调用
    4. Looper中的一些方法:prepare( )、loop( )、sThreadLocal.set ( new Looper (…) )
    5. loop( )方法,此处为一个死循环,不断的从MessageQueue中读取数据queue.next( ),如果没有数据就return,有数据则回调message.target.dispatchMessage( ),target为handler,在dispatchMessage( )方法中调用了handleMessage( )

AsyncTask

  1. 是什么:是一个封装了线程池和handler的异步框架
  2. 核心点:
    1. 三个参数,AsyncTask接受三个泛型参数 ( params, progress, result ),分别为参数、进度值、结果,也可全取void
    2. 四个方法:doInBackground(String… strings)(必须要重写)、onPrepareExcute( )(主线程,用于初始化,比如显示进度条)、onPostExcute( )(主线程,doInBackground执行完毕,更新UI等)、onProgressUpdata( )(主线程,更新进度条,需要在doInBackground调用publicProgress)
    3. 原理:线程池中的工作线程执行doInBackground中的异步任务,然后通过内部的handler发送消息
    4. 内存泄漏:静态内部类持有外部类的匿名引用,采用弱引用的方式

HandlerThread

  1. 是什么:本质是一个线程类,继承于thread
  2. 原理:在run方法中初始化了一个Looper对象,调用Looper的loop( )方法,不断的从MessageQueue中读取数据,没有消息则会阻塞;quit( )会清空所有的消息,quitSafely( )只会清空延时消息,无论调用了哪一个方法,Looper的循环就结束了。quit( )是MessageQueue的方法,作用是清空消息并且给变量置空、释放资源

IntentService

  1. 是什么:继承于Service,可以用来执行耗时操作,当任务执行完毕后服务会自动停止,不需要我们手动去停止,每个耗时操作会以一个队列的形式在IntentService的onHandleIntent( )回调中执行
  2. 和Service的区别:service运行在主线程,所以不能进行耗时操作,而IntentService则为了解决这个问题产生的
  3. 原理:
    1. 继承于Service,所以有着和Service一样的操作方式
    2. 在onCreate( )中,初始化了一个HandlerThread和Handler(创建一个HandlerThread,并获得其Looper对象,将Looper传入Handler创建一个Handler),借此构建了一个具有消息循环机制的后台线程
    3. 非常适合做一次性的后台任务,如下载一个文件,下载完成后自动销毁

View绘制机制

事件分发机制

ListView

  1. 优化:
    1. 复用convertView
    2. 使用viewHolder,频繁的findView会消耗内存
    3. 分页加载
    4. 图片缓存(图片框架加载)
    5. 滑动过程中不加载图片

动画机制

自定义View

Serializable和Parcelable

  1. Serializable是Java提供的一个序列化接口,需要提供一个序列化ID用于序列化和反序列化(需要大量的I/O操作,开销大,效率低,但是数据持久,适合序列化到本地的数据)
  2. Parcelable,Android SDK内部提供的一个序列化接口,不需要大量的I/O操作,开销小,效率高,适合内存序列化

Android各版本特性

  1. 5.0:MD风格,推出很多新控件如RecyclerView
  2. 6.0:动态权限,对于危险权限需要用户授权
  3. 7.0:文件访问权限,fileProvider
  4. 8.0:静态注册的广播不再适用;APP logo适配;允许安装未知来源应用;透明主题的Activity不能设置界面方向
  5. 9.0:前台service必须要申请 FOREGROUND_SERVICE 权限
  6. 10.0:暗黑模式;

Intent

  1. 显示意图:常见的就是activity的跳转,指明明确的组件名称
  2. 隐式意图
    1. 常见的是跳转到通讯录等,不指明明确的组件名称
    2. 组成成分:action(动作)、category(附加信息)、data(数据)、type(类型)等
  3. 自定义隐式意图:
    1. 在清单文件中注册intent-filter标签中申明action、category等
    2. 如何使用:在代码中使用intent启动这个自定义意图,注意setData( )和setType( )会互相清除,如果需要使用,则可以使用setDataAndType( )
  4. bundle和intent传参的区别
    1. intent.putExtra(…)实则还是调用的是bundle方式
    2. bundle是一个封装类,内部采用的是ArrayMap
    3. bundle更适合传递对象,intent适合传递单个字段

对话框

  1. dialog
    1. 是在window上addView和removeView的操作,window是个抽象概念,构造方法中创建了一个phoneWindow,windowManager提供了支持
    2. 在show( )方法中,有个onStart( )的空方法,可以在显示dialog前做一些操作,因此常见的一些自定义dialog样式,会在onStart( )方法中设置window的样式
    3. dismiss( )方法是线程安全的,当中根据Looper判断当前是否在主线程,主线程才去操作dismissDialog( ),非主线程则通过handler发送到主线程
  2. popupwindow
  3. toast

Context

  1. Context是一个抽象类,有两个具体的实现子类:ContextImpl、ContextWrapper
  2. Context数量 = Activity数量 + Service数量 + 进程数
  3. application、service都继承于ContextWrapper,activity继承于ContextThemeWrapper
  4. 不推荐用application去startActivity,因为非activity类型的context没有任务栈,service同理
  5. getApplication( )和getApplicationContext( )的本质是一样的,都是application实例,区别在于作用域,getApplication( )方法只能在activity、service中调用到,而getAppcationContext( )可以在其他场景调用
  6. 关于context的内存泄漏,主要是要避免context和引用者的生命周期不一致

系统架构与系统源码

冷启动和热启动

  1. 冷启动:后台没有该应用的进程,会先创建和初始化Application,再创建和初始化MainActivity等
  2. 热启动:后台有该应用的进程,可以在任务栈中看到,如用户按back、home等退出app。再次启动时,不会去创建Application,会去创建和初始化MainActivity等。
  3. 冷启动优化:
    1. 减少onCreate( )的工作量
    2. 不在application的初始化中做耗时操作,可以开启线程
    3. 布局优化
  4. APP启动白屏解决方案:由于绘制布局资源并不是在窗体绘制的第一步,所以会加载默认的背景色,可以在主题中设置默认的背景图@drawable/bg_splash

性能优化

  1. 流畅性
    1. 布局优化(更简单的ViewGroup、更少的布局层次、include、viewStup、merge)
    2. 启动优化(异步加载、分步加载、延迟加载)
  2. 稳定性
    1. 崩溃:代码健壮性、异常情况的考虑
    2. ANR:合理的处理耗时操作
  3. 节省
    1. 内存泄漏
    2. 减少资源文件的大小,webp
    3. apk大小

进程间通信

AIDL

  1. 定义:接口定义语言
  2. 定义AIDL接口:
    1. 在main目录定义一个aidl的目录,然后写上和app相同的包名,在其中申明xxx.aidl
    2. 在aidl中,常见类型可以直接使用,如int、String、boolean、List等,但如果是对象,需要实现Parcelable接口
    3. 非常见类型的参数,需要申明传输方向,如in(输入)、out(输出)、inout(可输入输出),默认为in
    4. 文件名要以“I”开头(这个说法是旧的,当前版本任意文件名都可以)
    5. 生成java文件,build —> make project
  3. 使用:
    1. 查看生成的java文件,可以发现代码内有一个Stub的静态抽象类,继承于Binder,实现了我们定义的aidl接口
    2. 实现接口:实现一个MyAIDL.Stub( ){ … },这步就是返回了一个Binder对象
    3. 开放接口,实现一个service,并重写onBind( )方法,传入上述的Binder对象
    4. Activity调用bindService( )绑定连接此服务,在onServiceConnect( )回调接收IBinder

Binder

  1. Binder是一个类,实现了IBinder接口
  2. Binder通信机制
    1. 在Android系统中,Binder通信机制由四个部分组成,分别是Client、Server、Service Manager、Binder Driver
    2. 内存空间分为两部分:用户空间(Client、Server、Service Manager)、内核空间(Binder Driver)
  3. 在Binder机制中,由Binder驱动负责完成这个中转工作
    1. Client向Server发起IPC请求时,Client会先将请求数据从用户空间拷贝到内核空间
    2. 数据拷贝到内核空间后,Binder驱动再将数据拷贝到Server用户空间的缓存中
  4. Service Manager:主要提供了service的添加和查询
  5. Client、Server、Service Manager都处于用户空间的不同进程中
  6. Binder跨进程通讯的步骤
    1. 初始化Service Manager:应用程序启动时,Service Manager和Binder通讯,Binder驱动新建Service Manager对应的Binder实体
    2. 注册Server:Server向Binder发起注册请求,如果Service Manager中没有此服务,则添加此服务
    3. Client获取远程服务:Client向Binder传递要查询的Server名称,Binder驱动将该请求转发给Service Manager,然后找到对应的Server并反馈给Client,Client收到Server对应的Binder引用后,会创建一个当前Server对应的远程服务
    4. Client通过代理调用Server:Client调用远程服务,远程服务和Binder驱动通讯,因为远程服务中带有Server的Binder引用,所以轻而易举的就能找到,进而将Client的请求发送给Server

Java

异常

  1. 异常通常分为三类:Error、编译时异常、运行时异常
  2. Finally:
    1. 一般情况下都会执行,如果在try、catch代码块中返回了return,则finally代码块内容会先return执行
    2. 如果程序被终止了,JVM退出了,finally代码块内容不会执行
  3. Final关键字
    1. 修饰类:不可被继承
    2. 修饰变量:常量,只可赋值一次
    3. 修饰方法:不可被重写

设计模式

  1. 单例

    //懒汉式
    //缺点:多线程同时访问时会有问题,缺乏同步
    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;
    
  2. Builder模式

  3. 适配器模式

    1. 现有类、目标类、目标接口
    2. 适用场景:现有类实现了某些功能,但是在目标场景下和目标接口定义不一致,则可以通过一个适配器类的方式,复用现有类的代码
    3. 优点
      1. 通过适配器,客户端可以调用同一接口,逻辑上更透明
      2. 解决了现有类和复用场景要求不一致的问题
      3. 将目标类和现有类解耦,引入一个适配器类,复用现有类的代码
    4. 实现方式
      1. 继承现有类,并且实现目标接口,调用父类的现有方法
      2. 实现目标接口,但是传入现有类,通过现有类的代理方式,调用现有的方法
    //已存在的,具有特殊功能,但不符合我们既有标准接口的类
    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();
    }
    
  4. 装饰模式

    1. 情景:动态的给一个对象添加额外的职责,比直接继承的子类更灵活
    2. 优点:通过使用不同的具体装饰类的互相组合,会创建出更多不同行为的组合
    3. 缺点:会产生比继承关系更多的对象,查错比较难,而且命名上都比较相似,不易分辨
  5. 外观设计模式

    1. 概念:提供了一个高层接口,便于调用
    2. 场景:在不断的迭代过程中,产生了很多小的类,这使得系统更具复用性、灵活。但是这也给那些不需要定制化的系统的使用带来了麻烦,所以可以提供一个高层的类,当然,也可以绕过这个高层类
  6. 观察者模式

    1. 概念:被观察者通过注册观察者,当被观察者发生变化时,通知观察者

    2. 实现步骤:

      1. 抽象观察者
      2. 抽象被观察者
      3. 实现具体的观察者
      4. 实现具体的被观察者
    3. 情景:当一方面依赖于另一方面时

      //抽象观察者
      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 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

  1. 使用
    1. 注册订阅者(register)、一般在页面销毁时注销订阅者(unregister)
    2. 声明订阅方法(@Subscribe)
    3. 发送事件(post、需要定义一个实体类)
    4. 注解@Subscribe解析:包含ThreadMode(一个枚举类、当前线程类型)、sticky(粘性事件)、priority(优先级)
    5. ThreadMode的类型:POSTING(运行在发送事件线程)、MAIN(UI线程)、BACKGROUND(后台线程)、ASYNC(订阅方法和发送事件始终不在一个线程,每次都会使用新的线程来运行),默认POSTING
    6. sticky 粘性事件:只会接收到最近一次发送的粘性事件,之前的接收不到
  2. 创建
    1. EventBus.getDefault( ):getDefault( )是一个获取实例的方法(单例,而且是双重验证的方式,保证在不同线程中也只有一个实例)
    2. 也可以通过EventBus.builder( )的形式创建自定义的EventBus,这里的建造者是EventBusBuilder,使用了建造者模式
  3. 注册
    1. 创建的时候,初始化了SubscriberMethodFinder对象 —> findSubscriberMethods( ) —> findUsingInfo( ) —> findUsingReflectionInSingleClass( ) —> checkAdd( ) —> checkAddWithMethodSignature( ) —> subscriberMethods.add( ) 至此获得了符合条件的SubsciberMethod
    2. findUsingReflectionInSingleClass( ):通过反射的方式,遍历类中所有的方法,找到符合要求的方法;比如判断了修饰符是否是Public、参数个数是否等于1、有没有Subscribe注解
    3. 总结:注册的过程就是把订阅方法和事件绑定起来,放入一个Map中
  4. 注销
    1. 根据当前订阅者获取它所有的事件,然后遍历这些事件,调用unsubscribeByEventType( ),传入订阅者和事件,解除两者之间的关系
  5. 发送事件
    1. 获取postingState,当中保存了线程信息、订阅者、事件等 —> 将事件添加到队列中 —> 遍历这个队列,postSingleEvent( ) —> postSingleEventForEventType( ) —> postToSubscription( ) —> 判断线程类型是否和当前一致,是的话调用invokeSubscriber( ),否则调用enqueue( ),通过handler或者runnable切换线程使得和ThreadMode一致
    2. invokeSubscriber( ),通过反射的方式调用订阅方法
  6. 粘性事件的发送和接收分析
    1. 通过postSticky( )发送消息
    2. EventBus不知道当前订阅者对应了哪个粘性事件,所以需要全部遍历一次,然后找到匹配的粘性事件后调用postSingleEventForEventType( ),回到了postToSubscription( )方法中判断当前ThreadMode的方法中
    3. postToSubscription( ),有三个重要的Poster,分别是mainThreadPoster、backgroundPoster、asyncPoster,mainTreadPoster类型是HandlerPoster继承于Handler,实现了Poster接口,通过handler的方式将事件发送到主线程;backgroundPoster和asyncPoster大致上都差不多,实现了Runnable接口,区别在于backgroundPoster会判断当前线程是否在运行,而asyncPoster不会判断,每次都使用一个新线程
  7. 总结:从整个EventBus中可以看出,事件作为被观察者,订阅方法是观察者,当事件发出或者发生变更时,订阅者都会立马收到通知。

Glide 4.x 缓存原理

  1. 简介:Glide缓存分成了两个模块,一个是内存缓存,一个是磁盘缓存;内存缓存是为了避免应用重复的将图片数据加载到内存中,磁盘缓存是为了避免应用重复的从网络或其他地方下载图片
  2. 使用:Glide默认开启内存缓存和磁盘缓存,可以通过配置去自定义关闭,或者定义缓存类型,如只缓存原始图,或者只缓存变换后的图
  3. 缓存key
    1. Glide的缓存key生成比较复杂,在3.x版本有数10个字段,4.x版本由8个字段组成,比较明显的就是3.x的版本用到了id字段(图片url),4.x使用的是Model;决定key生成的字段有很多,这里比较明显的是width和height,也就是说,宽高不一样,生成的key相应的也不一样
  4. 内存缓存
    1. 内存缓存用到了弱引用和LruCache来制定缓存策略
    2. 缓存的读取:通过key从缓存中读取,并且将缓存移除,然后将缓存加入到一个弱引用的HashMap当中,如果没有读取到缓存,则再去下载图片
    3. 缓存的写入:图片加载完成后,会通过handler切回到主线程中,然后在后续的方法中将资源put到弱引用的HashMap中,同时还有另外一个操作,通过acquire( )和release( )的变量进行计数,++或者--
    4. 总结:Glide将正在使用的图片加入到弱引用当中,不常用的图片加入到LruCache中
  5. 磁盘缓存
    1. Glide会优先读取内存缓存,在读取不到的时候,再去读取磁盘缓存
    2. 读取:通过key从缓存中读取缓存文件,如果文件不为null,则解码后返回
    3. 写入:图片加载完成后,判断是否允许写入磁盘,允许的话将图片写入磁盘缓存

你可能感兴趣的:(Android面试知识点、知识体系)