美文网首页
Android面试知识点、知识体系

Android面试知识点、知识体系

作者: 三天过去了 | 来源:发表于2019-09-26 16:02 被阅读0次

    自己面试准备的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启动白屏解决方案:由于绘制布局资源并不是在窗体绘制的第一步,所以会加载默认的背景色,可以在主题中设置默认的背景图<item name="android:background">@drawable/bg_splash</item>

    性能优化

    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<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

    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面试知识点、知识体系

          本文链接:https://www.haomeiwen.com/subject/wwbructx.html