Android基础面试题整理

文章目录

0、Android基础

0.1 【腾讯Bugly干货分享】Android ListView 与 RecyclerView 对比浅析—缓存机制

【腾讯Bugly干货分享】Android ListView 与 RecyclerView 对比浅析—缓存机制

0.2 面试官:请说一下你对约束布局的理解?

ConstraintLayout 完全解析 快来优化你的布局吧

0.3 面试官:请说一下Activity启动流程?(涉及源码 ActivityThread)

【凯子哥带你学Framework】Activity启动过程全解析

Activity启动流程
在这里插入图片描述
一共涉及到了四个进程,这四个进程分别是

  • Launcher进程
  • system_server进程
  • zygote进程
  • app进程

类说明:

  • ActivityManagerServices,简称AMS,服务端对象,负责系统中所有Activity的生命周期
  • ActivityThread,App的真正入口。当开启App之后,会调用main()开始运行,开启消息循环队列,这就是传说中的UI线程或者叫主线程。与ActivityManagerServices配合,一起完成Activity的管理工作
  • ApplicationThread,用来实现ActivityManagerService与ActivityThread之间的交互。在ActivityManagerService需要管理相关Application中的Activity的生命周期时,通过ApplicationThread的代理对象与ActivityThread通讯。
  • ApplicationThreadProxy,是ApplicationThread在服务器端的代理,负责和客户端的ApplicationThread通讯。AMS就是通过该代理与ActivityThread进行通信的。
  • Instrumentation,每一个应用程序只有一个Instrumentation对象,每个Activity内都有一个对该对象的引用。Instrumentation可以理解为应用进程的管家,ActivityThread要创建或暂停某个Activity时,都需要通过Instrumentation来进行具体的操作。
  • ActivityStack,Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。
  • ActivityRecord,ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。
  • TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这个概念应该不陌生。

启动流程分析:

  • Launcher进程中点击桌面App图标,onclick事件触发,调用startActivity,在该方法中会通过instrumention采用Binder通信向system_server(AMS)进程发起startActivity请求;

  • 调用startActivity(intent)方法,该方法在Activity类实现。startActivity调用的还是startActivityForResult()

  • IPC过程
    在这里插入图片描述

  • system_server进程接收到请求后,向zygote进程发送创建进程的请求;

  • Zygote进程fork出新的子进程,即App进程;

  • App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;

  • system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;

  • App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;

  • 主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。

  • 到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。

参考文档

0.3.1 Zygote是什么?有什么作用?

Android系统底层基于Linux Kernel, 当Kernel启动过程会创建init进程, 该进程是所有用户空间的鼻祖, init进程会启动ServiceManager(Binder服务管家)、 Zygote进程(Java进程的鼻祖)。Zygote进程会创建 system_server进程以及各种App进程。

Zygote进程:是Android系统的首个Java进程,Zygote是所有Java进程的父进程,包括 system_server进程以及所有的App进程都是Zygote的子进程,注意这里说的是子进程,而非子线程。

0.3.2 SystemServer是什么?有什么作用?它与zygote的关系是什么?

  • SystemServer也是一个进程,而且是由zygote进程fork出来的
  • 里面重要的服务都是在这个进程里面开启的,比如
    ActivityManagerService、PackageManagerService、WindowManagerService等等

在zygote开启的时候,会调用ZygoteInit.main()进行初始化

public static void main(String argv[]) {
    
    

     ...ignore some code...

    //在加载首个zygote的时候,会传入初始化参数,使得startSystemServer = true
     boolean startSystemServer = false;
     for (int i = 1; i < argv.length; i++) {
    
    
                if ("start-system-server".equals(argv[i])) {
    
    
                    startSystemServer = true;
                } else if (argv[i].startsWith(ABI_LIST_ARG)) {
    
    
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
    
    
                    socketName = argv[i].substring(SOCKET_NAME_ARG.length());
                } else {
    
    
                    throw new RuntimeException("Unknown command line argument: " + argv[i]);
                }
            }

            ...ignore some code...

         //开始fork我们的SystemServer进程
     if (startSystemServer) {
    
    
                startSystemServer(abiList, socketName);
         }

     ...ignore some code...

}

0.3.3 ActivityManagerService是什么?什么时候初始化的?有什么作用?

  • ActivityManagerService,简称AMS,服务端对象,负责系统中所有Activity的生命周期
  • ActivityManagerService进行初始化的时机很明确,就是在SystemServer进程开启的时候,就会初始化ActivityManagerService。从下面的代码中可以看到
public final class SystemServer {
    
    

    //zygote的主入口
    public static void main(String[] args) {
    
    
        new SystemServer().run();
    }

    public SystemServer() {
    
    
        // Check for factory test mode.
        mFactoryTestMode = FactoryTest.getMode();
    }

    private void run() {
    
    

        ...ignore some code...

        //加载本地系统服务库,并进行初始化 
        System.loadLibrary("android_servers");
        nativeInit();

        // 创建系统上下文
        createSystemContext();

        //初始化SystemServiceManager对象,下面的系统服务开启都需要调用SystemServiceManager.startService(Class<T>),这个方法通过反射来启动对应的服务
        mSystemServiceManager = new SystemServiceManager(mSystemContext);

        //开启服务
        try {
    
    
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
        } catch (Throwable ex) {
    
    
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        }

        ...ignore some code...

    }

    //初始化系统上下文对象mSystemContext,并设置默认的主题,mSystemContext实际上是一个ContextImpl对象。调用ActivityThread.systemMain()的时候,会调用ActivityThread.attach(true),而在attach()里面,则创建了Application对象,并调用了Application.onCreate()。
    private void createSystemContext() {
    
    
        ActivityThread activityThread = ActivityThread.systemMain();
        mSystemContext = activityThread.getSystemContext();
        mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
    }

    //在这里开启了几个核心的服务,因为这些服务之间相互依赖,所以都放在了这个方法里面。
    private void startBootstrapServices() {
    
    

        ...ignore some code...

        //初始化ActivityManagerService
        mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);

        //初始化PowerManagerService,因为其他服务需要依赖这个Service,因此需要尽快的初始化
        mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);

        // 现在电源管理已经开启,ActivityManagerService负责电源管理功能
        mActivityManagerService.initPowerManagement();

        // 初始化DisplayManagerService
        mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class);

    //初始化PackageManagerService
    mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller,
       mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

    ...ignore some code...

    }

}
  • 经过上面这些步骤,我们的ActivityManagerService对象已经创建好了,并且完成了成员变量初始化。而且在这之前,调用createSystemContext()创建系统上下文的时候,也已经完成了mSystemContext和ActivityThread的创建。注意,这是系统进程开启时的流程,在这之后,会开启系统的Launcher程序,完成系统界面的加载与显示
  • AMS属于SystemServer进程里面创建的,这是非常重要的关键点。

App和AMS(SystemServer进程)还有zygote进程分属于三个独立的进程,他们之间如何通信呢?(App与AMS通过Binder进行IPC通信,AMS(SystemServer进程)与zygote通过Socket进行IPC通信。)

那么AMS有什么用呢?打开一个App的话,需要AMS去通知zygote进程,然后zygote会fork出一个子进程,除此之外,其实所有的Activity的开启、暂停、关闭都需要AMS来控制,所以我们说,AMS负责系统中所有Activity的生命周期

0.3.4 Launcher是什么?什么时候启动的?

Launcher本质上也是一个应用程序,和我们的App一样,也是继承自Activity

public final class Launcher extends Activity
        implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                   View.OnTouchListener {
    
    
                   }

当我们点击快捷图标的时候:

public void onClick(View v) {
    
    
        ...ignore some code...

        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
    
    
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
        //开始开启Activity咯~
            boolean success = startActivitySafely(v, intent, tag);

            if (success && v instanceof BubbleTextView) {
    
    
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {
    
    
            //如果点击的是图标文件夹,就打开文件夹
            if (v instanceof FolderIcon) {
    
    
                FolderIcon fi = (FolderIcon) v;
                handleFolderClick(fi);
            }
        } else if (v == mAllAppsButton) {
    
    
        ...ignore some code...
        }
    }

会调用到:

Launcher.startActivitySafely()

boolean startActivitySafely(View v, Intent intent, Object tag) {
    
    
    boolean success = false;
    try {
    
    
        success = startActivity(v, intent, tag);
    } catch (ActivityNotFoundException e) {
    
    
        Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();           
    }
    return success;
}

调用了startActivity(v, intent, tag)

boolean startActivity(View v, Intent intent, Object tag) {
    
    

        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
    
    
            boolean useLaunchAnimation = (v != null) &&
                    !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);

            if (useLaunchAnimation) {
    
    
                if (user == null || user.equals(android.os.Process.myUserHandle())) {
    
    
                    startActivity(intent, opts.toBundle());
                } else {
    
    
                    launcherApps.startMainActivity(intent.getComponent(), user,
                            intent.getSourceBounds(),
                            opts.toBundle());
                }
            } else {
    
    
                if (user == null || user.equals(android.os.Process.myUserHandle())) {
    
    
                    startActivity(intent);
                } else {
    
    
                    launcherApps.startMainActivity(intent.getComponent(), user,
                            intent.getSourceBounds(), null);
                }
            }
            return true;
        } catch (SecurityException e) {
    
    
        ...
        }
        return false;
}

最后面就会调用:

Activity.startActivityForResult()

0.4 Android SystemUI?

史上最详细的Android系统SystemUI 启动过程详细解析

0.5 当面试官问道:“Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么?”你会怎么答呢?

当面试官问道:“Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么?”你会怎么答呢?

1、第三方库

1.1 OkHttp

OkHttp内部逻辑流程图:
在这里插入图片描述

先来一发灵魂拷问四连击:

  • addInterceptoraddNetworkInterceptor有什么区别?
  • 网络缓存如何实现的?
  • 网络连接怎么实现复用
  • OkHttp如何做网络监控

1.1.1 OkHttp基本实现原理(责任链)

OkHttp的内部实现通过一个责任链模式完成,将网络请求的各个阶段封装到各个链条中,实现了各层的解耦

1.1.2 OkHttp源码分析

OkHttp源码分析

核心类:

  • OkHttpClient
  • Request 和Response
  • RealCall
1.1.2.1 OkHttpClient

这个是整个OkHttp的核心管理类,所有的内部逻辑和对象归OkHttpClient统一来管理,它通过Builder构造器生成,构造参数和类成员很多。

1.1.2.2 Request 和Response
  • Request是我们发送请求封装类,内部有url, header , methodbody等常见的参数

  • Response是请求的结果,包含code, message, header,body

这两个类的定义是完全符合Http协议所定义的请求内容和响应内容。

1.1.2.3 RealCall
  • 负责请求的调度(同步的话走当前线程发送请求,异步的话则使用OkHttp内部的线程池进行);
  • 同时负责构造内部逻辑责任链,并执行责任链相关的逻辑,直到获取结果。

虽然OkHttpClient是整个OkHttp的核心管理类,但是真正发出请求并且组织逻辑的是RealCall类,它同时肩负了调度和责任链组织的两大重任

RealCall类并不复杂,有两个最重要的方法,execute() 和 enqueue(),一个是处理同步请求,一个是处理异步请求。跟进enqueue的源码后发现,它只是通过异步线程和callback做了一个异步调用的封装,最终逻辑还是会调用到execute()这个方法,然后调用了getResponseWithInterceptorChain()获得请求结果。

1.1.3 OkHttp拦截器(List顺序)

#RealCall
fun getResponseWithInterceptorChain(): Response {
    
    
    //创建拦截器数组
    val interceptors = mutableListOf<Interceptor>()
    //添加应用拦截器
    interceptors += client.interceptors
    //添加重试和重定向拦截器
    interceptors += RetryAndFollowUpInterceptor(client)
    //添加桥接拦截器
    interceptors += BridgeInterceptor(client.cookieJar)
    //添加缓存拦截器
    interceptors += CacheInterceptor(client.cache)
    //添加连接拦截器
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
    
    
      //添加网络拦截器
      interceptors += client.networkInterceptors
    }
    //添加请求拦截器
    interceptors += CallServerInterceptor(forWebSocket)

    //创建责任链
    val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
        client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
    ...
    try {
    
    
      //启动责任链
      val response = chain.proceed(originalRequest)
      ...
      return response
    } catch (e: IOException) {
    
    
      ...
    }
  }

不包括自定义的拦截器的话,系统默认存在五个拦截器。

1.1.3.1 retryAndFollowUpInterceptor 失败和重定向拦截器

在这里插入图片描述

  • 当请求内部抛出异常时,判定是否需要重试
  • 当响应结果是3xx重定向时,构建新的请求并发送请求

重试规则:

  • 规则1: client的retryOnConnectionFailure参数设置为false,不进行重试
  • 规则2: 请求的body已经发出,不进行重试
  • 规则3: 特殊的异常类型不进行重试(如ProtocolExceptionSSLHandshakeException等)
  • 规则4: 没有更多的route(包含proxy和inetaddress),不进行重试
1.1.3.2 BridgeInterceptor 封装request和response拦截器

BridageInterceptor 拦截器的功能如下:

  • 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应,是从应用程序代码到网络代码的桥梁
  • 设置内容长度,内容编码
  • 设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
  • 添加cookie
  • 设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现连接复用的必要步骤
1.1.3.3 CacheInterceptor 缓存相关的过滤器,负责读取缓存直接返回、更新缓存

CacheInterceptor 拦截器的逻辑流程如下:

  • 通过Request尝试到Cache中拿缓存,当然前提是OkHttpClient中配置了缓存,默认是不支持的。
  • 根据response,time,request创建一个缓存策略,用于判断怎样使用缓存。
  • 如果缓存策略中设置禁止使用网络,并且缓存又为空,则构建一个Response直接返回,注意返回码=504
  • 缓存策略中设置不使用网络,但是又缓存,直接返回缓存
  • 接着走后续过滤器的流程,chain.proceed(networkRequest)
  • 当缓存存在的时候,如果网络返回的Resposne为304,则使用缓存的Resposne。
  • 构建网络请求的Resposne
  • 当在OkHttpClient中配置了缓存,则将这个Resposne缓存起来。
  • 缓存起来的步骤也是先缓存header,再缓存body
    返回Resposne
1.1.3.4 ConnectInterceptor 连接服务,负责和服务器建立连接 这里才是真正的请求网络

socket连接和tls连接

讲解太长,看这里吧

最终会获得 一个Exchange的类,这个类有两个实现,一个是Http1ExchangeCodec,一个是Http2Exchangecodec,分别对应的是Http1协议和Http2协议。

1.1.3.5 CallServerInterceptor 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据 进行http请求报文的封装与请求报文的解析

传输http的头部body数据

CallServerInterceptor由以下步骤组成:

  • 向服务器发送 request header
  • 如果有 request body,就向服务器发送
  • 读取 response header,先构造一个 Response 对象
  • 如果有 response body,就在 3 的基础上加上 body 构造一个新的 Response 对象

这里我们可以看到,核心工作都由 HttpCodec 对象完成,而 HttpCodec 实际上利用的是 Okio,而 Okio 实际上还是用的 Socket,只不过一层套一层,层数有点多。

1.1.4 问题

1.1.4.1 addInterceptor与addNetworkInterceptor的区别

二者通常的叫为应用拦截器网络拦截器,从整个责任链路来看:

  • 应用拦截器是最先执行的拦截器,也就是用户自己设置request属性后的原始请求
  • 网络拦截器位于ConnectInterceptorCallServerInterceptor之间,此时网络链路已经准备好,只等待发送请求数据。

首先,应用拦截器在RetryAndFollowUpInterceptorCacheInterceptor之前,所以一旦发生错误重试或者网络重定向,网络拦截器可能执行多次,因为相当于进行了二次请求,但是应用拦截器永远只会触发一次。另外如果在CacheInterceptor中命中了缓存就不需要走网络请求了,因此会存在短路网络拦截器的情况。

其次,除了CallServerInterceptor,每个拦截器都应该至少调用一次realChain.proceed方法。实际上在应用拦截器这层可以多次调用proceed方法(本地异常重试)或者不调用proceed方法(中断),但是网络拦截器这层连接已经准备好,可且仅可调用一次proceed方法。

最后,从使用场景看,应用拦截器因为只会调用一次,通常用于统计客户端的网络请求发起情况;而网络拦截器一次调用代表了一定会发起一次网络通信,因此通常可用于统计网络链路上传输的数据

1.1.4.2 网络缓存机制 CacheInterceptor(OKHttp的缓存实现)

这里涉及到HTTP缓存知识。

  • 第一次拿到响应后根据头信息决定是否缓存。
  • 下次请求时判断是否存在本地缓存,是否需要使用对比缓存、封装请求头信息等等。
  • 如果缓存失效或者需要对比缓存则发出网络请求,否则使用本地缓存。

OKHttp内部使用Okio来实现缓存文件的读写

缓存文件分为CleanFiles和DirtyFiles,CleanFiles用于读,DirtyFiles用于写,他们都是数组,长度为2,表示两个文件,即缓存的请求头和请求体;同时记录了缓存的操作日志,记录在journalFile中。

开启缓存需要在OkHttpClient创建时设置一个Cache对象,并指定缓存目录和缓存大小,缓存系统内部使用LRU作为缓存的淘汰算法。

最后需要注意的一点是,OKHttp默认只支持get请求的缓存

1.1.5 OkHttp设计模式

1.1.5.1 单例模式

OkHttpClient, 可以通过 new OkHttpClient() 或 new OkHttpClient.Builder() 来创建对象, 但是—特别注意, OkHttpClient() 对象最好是共享的, 建议使用单例模式创建。 因为每个 OkHttpClient 对象都管理自己独有的线程池和连接池。

1.1.5.2 外观模式

OKHttpClient 里面组合了很多的类对象。其实是将OKHttp的很多功能模块,全部包装进这个类中,让这个类单独提供对外的API,这种设计叫做外观模式(外观模式:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口

1.1.5.3 建造者模式(Builder)

OkHttpClient 比较复杂, 太多属性, 而且客户的组合需求多样化, 所以OKhttp使用建造者模式(Build模式:使用多个简单的对象一步一步构建成一个复杂的对象,一个 Builder 类会一步一步构造最终的对象

1.1.5.4 责任链模式

OkHttp3 的拦截器链中, 内置了5个默认的拦截器,分别用于重试、请求对象转换、缓存、链接、网络读写(责任链模式:为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。)

1.1.5.5 策略模式

CacheInterceptor 实现了数据的选择策略, 来自网络还是来自本地? 这个场景也是比较契合策略模式场景, CacheInterceptor 需要一个策略提供者提供它一个策略(锦囊), CacheInterceptor 根据这个策略去选择走网络数据还是本地缓存。

1.1.6 总结

在这里插入图片描述

1.2 EventBus

EventBus核心其实就是三幅图,这三幅图涉及的是三个HashMap表,弄懂这三幅图那么EventBus就懂了。

1.2.1 订阅者Subscribe 和订阅事件 Event(typeBySubscriber)

onStart{
    
    
  EventBus.getDefault().register(this);
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent1(Event1 event) {
    
    
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent2(Event2 event) {
    
    
}

onStop{
    
    
  EventBus.getDefault().register(this);
}

注册监听的是activity,称为subscriber,在activity中监听了Event1和Event2两个事件.

在这里插入图片描述
一个Subscribe对应多个Event,Subsribe就是上面通过register方法注册的对象,比如activity。这幅图对应EventBus中一个Map结构:

private final Map<Object, List<Class<?>>> typesBySubscriber;

EventBus会在对象register时,使用反射机制,遍历对象的方法,将带有@Subscribe标签并且合法的方法加入到typesBySubscriber。typesBySubscriber是HashMap形式,key是注册的对象本身,由于一个注册的对象中可能有多个监听事件,所以value是用list保存的Event。

public void register(Object subscriber) {
    
    
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
    
    
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
    
    
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
    // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    
    
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
    
    
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
    }

上面的代码主要做:

  • 通过反射遍历注册对象的方法,获取其中带有@Subscribe标签的方法并且放在一个列表中,最后以注册对象为key,@Subscribe的方法列表作为value放在HashMap中,就是上图的形式。
1.2.1.1 1、为什么要将注册监听对象作为key,监听事件列表作为value放在HashMap中?

要弄懂一个问题,EventBus是观察者模式,上面的activity也就是subscribe是订阅者,activity中的event是订阅事件,一个订阅者可以订阅多个事件,移除一个订阅者的监听事件时,应该将其中所有的event的事件移除。 也就是说在反注册的时候,会通过Subsribe来查找到其中所有event进行反注册。

1.2.2 订阅事件 Event 和订阅者 Subscription

在这里插入图片描述
这种表关系是eventsubsciption的对应关系,比如在Android中多个activity可能会注册监听同一个event事件,所以在执行:

EventBus.getDefault().post(new Event1());

的时候所有注册监听了Event1的监听都会要会收到回调,看下subsciption的结构
在这里插入图片描述
subsciption中包含,订阅的事件订阅者本身

1.2.2.1 为什么需要保存Event和subsribe对应的关系表?

这是因为一个Event可能会有被多个subsribe订阅,所以有当执行post(Event)的时候会查找到所有订阅了Event事件的subscribe并调用其中的event方法。下面看下post方法:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    
    
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
    
    
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
    
    
            for (Subscription subscription : subscriptions) {
    
    
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
    
    
                    postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
    
    
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
    
    
                    break;
                }
            }
            return true;
        }
        return false;
    }
    }

post和postSticky主要都会调用到上面的方法,上面方法中subscriptionsByEventType.get(eventClass)就是通过event的类型找上面的表中找到对应的subscriptions进行通知的。

1.2.3 postSticky (延迟)

在看第三幅图之前思考一个问题,postSticky到底是怎么执行的?为什么先执行postSticky,后执行register还是可以监听到event事件? 先看postSticky代码:

public void postSticky(Object event) {
    
    
        synchronized (stickyEvents) {
    
    
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
}

原来执行postSticky的时候会将event.getclass和event保存起来,然后再看下subscribe代码:

if (subscriberMethod.sticky) {
    
    
            if (eventInheritance) {
    
    
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
    
    
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
    
    
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
} else {
    
    
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
  • 先判断注册监听的event是不是sticky的如果是就会用stickEvents表中找到stickyEvent
  • 如果注册的事件event和stickyEvent一样那么就会执行一次postToSubscription方法,也就是调用注册的方法执行。

在这里插入图片描述

1.2.4 ThreadMode 方式

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    
    
        switch (subscription.subscriberMethod.threadMode) {
    
    
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
    
    
                    invokeSubscriber(subscription, event);
                } else {
    
    
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
    
    
                    backgroundPoster.enqueue(subscription, event);
                } else {
    
    
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }
  • POSTING
    事件的处理在和事件的发送在相同的进程,Subscriber会在post event的所在线程回调,故它不需要切换线程来分发事件,因此开销最小。它要求task完成的要快,不能阻塞MainThread,适用简单的task。
子线程发布->子线程接收,主线程发布->主线程接收
  • MAIN
    Subscriber会在主线程(有时候也被叫做UI线程)回调,如果post event所在线程是MainThread,则可直接回调。注意不能阻塞主线程。
子线程发布->主线程接收,主线程发布->主线程接收
  • BACKGROUND
    Subscriber会在后台线程中回调。如果post event所在线程不是MainThread,那么可直接回调;如果是MainThread,EventBus会用单例background thread来有序地分发事件。注意不能阻塞background thread。
主线程发布->子线程接收、子线程发布->子线程接收
  • ASYNC
    当处理事件的Method是耗时的,需要使用此模式。尽量避免同时触发大量的耗时较长的异步操作,EventBus使用线程池高效的复用已经完成异步操作的线程。
主线程发布->创建新的子线程接收、子线程发布->创建新的子线程接收(两个子线程不同)

1.2.5 缺点

  • 使用的时候有定义很多event类
  • event在注册的时候会调用反射去遍历注册对象的方法在其中找出带有@subscriber标签的方法,性能不高。
  • 需要自己注册和反注册,如果忘了反注册就会导致内存泄漏

1.2.6 总结

  • 要理解EventBus就要从register,unRegister,post,postSticky方法入手。要理解register实质上是将订阅对象(比如activity)中的每个带有subscriber的方法找出来,最后获得调用的就是这些方法。订阅对象(比如activity)是一组event方法的持有者。
  • 后注册的对象中sticky方法能够收到之前的stickyEvent方法的原因是EventBus中维护了stickyEvent的hashMap表,在subsribe注册的时候就遍历其中有没有注册监听stickyEvent如果有就会执行一次回调。

1.3 Retrofit

参考文档

Retrofit 是一个 RESTfulHTTP 网络请求框架的封装。注意这里并没有说它是网络请求框架,主要原因在于网络请求的工作并不是 Retrofit 来完成的。Retrofit 2.0 开始内置 OkHttp,前者专注于接口的封装,后者专注于网络请求的高效,二者分工协作,宛如古人的『你耕地来我织布』,小日子别提多幸福了。
在这里插入图片描述
我们的应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作,在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,后者根据用户的需求对结果进行解析的过程。

Retrofitting OkHttp

1.3.1 Retrofit基本使用

例子:


// 定义
 public interface GitHubService {
    
    
   @GET("users/{user}/repos")
   Call<List<Repo>> listRepos(@Path("user") String user);
 }

// 构造
 Retrofit retrofit = new Retrofit.Builder()
     .baseUrl("https://api.github.com/")
     .build();

 GitHubService service = retrofit.create(GitHubService.class);

// 调用
Call<List<Repo>> repos = service.listRepos("octocat");

// 同步调用
 List<Repo> data = repos.execute(); 

 // 异步调用
 repos.enqueue(new Callback<List<Repo>>() {
    
    
             @Override
             public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
    
    
                 List<Repo> data = response.body();
             }

             @Override
             public void onFailure(Call<List<Repo>> call, Throwable t) {
    
    
                 t.printStackTrace();
             }
});
1.3.1.1 Url 配置

支持 GET/POST/PUT/DELETE/HEAD/PATCH

在这里插入图片描述

1.3.1.2 参数类型

发请求时,需要传入参数,Retrofit 通过注解的形式令 Http 请求的参数变得更加直接,而且类型安全。

1.3.1.2.1 Query & QueryMap
@GET("/list")
 Call<ResponseBody> list(@Query("page") int page);

Query 其实就是 Url 中 ‘?’ 后面的 key-value.

@GET("News")
Call<NewsBean> getItem(@QueryMap Map<String, String> map);
1.3.1.2.2 Field & FieldMap

使用 POST 提交表单的场景是刚需了,怎么提呢?

@FormUrlEncoded
@POST("/")
Call<ResponseBody> example(
       @Field("name") String name,
       @Field("occupation") String occupation
       );
@POST("/")
@FormUrlEncoded
Call<WeatherBeans> requestWeatherBeans(@FieldMap Map<String, String> fields);

1.3.1.2.3 Part & PartMap

这个是用来上传文件的。话说当年用 HttpClient 上传个文件老费劲了,一会儿编码不对,一会儿参数错误(也怪那时段位太低吧TT)。。。可是现在不同了,自从有了 Retrofit,妈妈再也不用担心文件上传费劲了~~~

public interface FileUploadService {
    
      
     @Multipart
     @POST("upload")
     Call<ResponseBody> upload(@Part("description") RequestBody description,
                               @Part MultipartBody.Part file);
 }

如果你需要上传文件,和我们前面的做法类似,定义一个接口方法,需要注意的是,这个方法不再有 @FormUrlEncoded 这个注解,而换成了 @Multipart,后面只需要在参数中增加 Part 就可以了。也许你会问,这里的 Part 和 Field 究竟有什么区别,其实从功能上讲,无非就是客户端向服务端发起请求携带参数的方式不同,并且前者可以携带的参数类型更加丰富,包括数据流。也正是因为这一点,我们可以通过这种方式来上传文件,下面我们就给出这个接口的使用方法:

//先创建 service
 FileUploadService service = retrofit.create(FileUploadService.class);

 //构建要上传的文件
 File file = new File(filename);
 RequestBody requestFile =
         RequestBody.create(MediaType.parse("application/otcet-stream"), file);

 MultipartBody.Part body =
         MultipartBody.Part.createFormData("aFile", file.getName(), requestFile);

 String descriptionString = "This is a description";
 RequestBody description =
         RequestBody.create(
                 MediaType.parse("multipart/form-data"), descriptionString);

 Call<ResponseBody> call = service.upload(description, body);
 call.enqueue(new Callback<ResponseBody>() {
    
    
   @Override
   public void onResponse(Call<ResponseBody> call,
                          Response<ResponseBody> response) {
    
    
     System.out.println("success");
   }

   @Override
   public void onFailure(Call<ResponseBody> call, Throwable t) {
    
    
     t.printStackTrace();
   }
 });
1.3.1.3 Converter,让你的入参和返回类型丰富起来
1.3.1.3.1 RequestBodyConverter

上面传输文件中,我们:
在这里插入图片描述
改造一下:

public interface FileUploadService {
    
      
     @Multipart
     @POST("upload")
     Call<ResponseBody> upload(@Part("description") RequestBody description,
         //注意这里的参数 "aFile" 之前是在创建 MultipartBody.Part 的时候传入的
         @Part("aFile") File file);
 }

(默认使用 GsonRequestBodyConverter)接下来我们就自己实现一个 FileRequestBodyConverter:

static class FileRequestBodyConverterFactory extends Converter.Factory {
    
    
     @Override
     public Converter<File, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    
    
       return new FileRequestBodyConverter();
     }
   }

   static class FileRequestBodyConverter implements Converter<File, RequestBody> {
    
    

     @Override
     public RequestBody convert(File file) throws IOException {
    
    
       return RequestBody.create(MediaType.parse("application/otcet-stream"), file);
     }
   }
addConverterFactory(new FileRequestBodyConverterFactory())
1.3.1.3.2 ResponseBodyConverter

Retrofit 也支持自定义 ResponseBodyConverter(默认 GsonResponseBodyConverter)。

问题来了,如果请求得到的 Json 字符串与返回值类型不对应,比如:

{
    
    "err":0, "content":"This is a content.", "message":"OK"}

而实体类型为:

class Result{
    
    
     int code;//等价于 err
     String body;//等价于 content
     String msg;//等价于 message
 }

自定义Converter解决:

   static class ArbitraryResponseBodyConverterFactory extends Converter.Factory{
    
    
     @Override
     public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
    
    
       return super.responseBodyConverter(type, annotations, retrofit);
     }
   }

   static class ArbitraryResponseBodyConverter implements Converter<ResponseBody, Result>{
    
    

     @Override
     public Result convert(ResponseBody value) throws IOException {
    
    
       RawResult rawResult = new Gson().fromJson(value.string(), RawResult.class);
       Result result = new Result();
       result.body = rawResult.content;
       result.code = rawResult.err;
       result.msg = rawResult.message;
       return result;
     }
   }

   static class RawResult{
    
    
     int err;
     String content;
     String message;
   }

1.3.2 Retrofit原理剖析

这里涉及到了动态代理,参考Java基础面试题整理。

public <T> T create(final Class<T> service) {
    
    
     Utils.validateServiceInterface(service);
     if (validateEagerly) {
    
    
       eagerlyValidateMethods(service);
     }
     //这里返回一个 service 的代理对象
     return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] {
    
     service },
         new InvocationHandler() {
    
    
           private final Platform platform = Platform.get();

           @Override public Object invoke(Object proxy, Method method, Object... args)
               throws Throwable {
    
    
             // If the method is a method from Object then defer to normal invocation.
             if (method.getDeclaringClass() == Object.class) {
    
    
               return method.invoke(this, args);
             }
             //DefaultMethod 是 Java 8 的概念,是定义在 interface 当中的有实现的方法
             if (platform.isDefaultMethod(method)) {
    
    
               return platform.invokeDefaultMethod(method, service, proxy, args);
             }
             //每一个接口最终实例化成一个 ServiceMethod,并且会缓存
             ServiceMethod serviceMethod = loadServiceMethod(method);

             //由此可见 Retrofit 与 OkHttp 完全耦合,不可分割
             OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
             //下面这一句当中会发起请求,并解析服务端返回的结果
             return serviceMethod.callAdapter.adapt(okHttpCall);
           }
         });
   }

任意方法最终都会 实际上调用的是这里的 InvocationHandler.invoke 方法~~

1.3.2.1 OkHttpCall

实际上每一个 OkHttpCall 都对应于一个请求,它主要完成最基础的网络请求,而我们在接口的返回中看到的 Call 默认情况下就是 OkHttpCall 了,如果我们添加了自定义的 callAdapter,那么它就会将 OkHttp 适配成我们需要的返回值,并返回给我们。

call接口定义:

 public interface Call<T> extends Cloneable {
    
    
   //同步发起请求
   Response<T> execute() throws IOException;
   //异步发起请求,结果通过回调返回
   void enqueue(Callback<T> callback);
   boolean isExecuted();
   void cancel();
   boolean isCanceled();
   Call<T> clone();
   //返回原始请求
   Request request();
 }
1.3.2.2 OkHttpCall.execute
@Override public Response<T> execute() throws IOException {
    
    
     //这个 call 是真正的 OkHttp 的 call,本质上 OkHttpCall 只是对它做了一层封装
     okhttp3.Call call;

     synchronized (this) {
    
    
       //处理重复执行的逻辑
       if (executed) throw new IllegalStateException("Already executed.");
       executed = true;

       if (creationFailure != null) {
    
    
         if (creationFailure instanceof IOException) {
    
    
           throw (IOException) creationFailure;
         } else {
    
    
           throw (RuntimeException) creationFailure;
         }
       }

       call = rawCall;
       if (call == null) {
    
    
         try {
    
    
           call = rawCall = createRawCall();
         } catch (IOException | RuntimeException e) {
    
    
           creationFailure = e;
           throw e;
         }
       }
     }

     if (canceled) {
    
    
       call.cancel();
     }
     //发起请求,并解析结果
     return parseResponse(call.execute());
   }

  • OkHttpCall 其实也是封装了 okhttp3.Call,在这个方法中,我们通过 okhttp3.Call发起了请求。

  • parseResponse 主要完成了由 okhttp3.Response 向 retrofit.Response 的转换,同时也处理了对原始返回的解析:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    
    
     ResponseBody rawBody = rawResponse.body();

     //略掉一些代码
     try {
    
    
       //在这里完成了原始 Response 的解析,T 就是我们想要的结果,比如 GitHubService.listRepos 的 List<Repo>
       T body = serviceMethod.toResponse(catchingBody);
       return Response.success(body, rawResponse);
     } catch (RuntimeException e) {
    
    
       // If the underlying source threw an exception, propagate that rather than indicating it was
       // a runtime exception.
       catchingBody.throwIfCaught();
       throw e;
     }
   }

至此,我们就拿到了我们想要的数据~~

1.3.2.3 结果适配,你是不是想用 RxJava?

前面我们已经提到过 CallAdapter 的事儿,默认情况下,它并不会对 OkHttpCall 实例做任何处理:

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
    
    
   static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

   @Override
   public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    
    
     ... 毫不留情的省略一些代码 ...
     return new CallAdapter<Call<?>>() {
    
    
       ... 省略一些代码 ...

       @Override public <R> Call<R> adapt(Call<R> call) {
    
    
         //看这里,直接把传入的 call 返回了
         return call;
       }
     };
   }
 }

现在的需求是,我想要接入 RxJava,让接口的返回结果改为 Observable:

addCallAdapterFactory(RxJavaCallAdapterFactory.create())

1.3.3 Mock Server

参考文献

1.4 Glide

1.5 Leakcanary原理浅析

Leakcanary原理浅析

2、性能优化

2.1 卡顿优化

相对于其他类型的性能指标,卡顿是能直接让用户产生视觉反馈的现象,比如App反应滞后于用户的操作,在严重的情况下会出现ANR。关乎用户体验的大事,是很容易遭到用户吐槽的(真理真理)。因此,开发人员平时写代码时必须要时刻提醒自己不要落入卡顿的陷阱之中。

2.1.1 卡顿原因

UI线程是基于queue中的message事件驱动的,事件 -> 执行 -> 下一个事件…,另一方面由于Android的帧率是60fps,也就是每16ms就会触发一次UI刷新,如果某个message的处理时间 > 16ms,就会导致接收到VSYNC信号的时候无法完成本次刷新操作,产生掉帧现象。

从本质上来讲,我们必须让UI线程的任何事件在16ms之内解决战斗

基于此,可能会导致卡顿的原因有三大类:

  • 1)事件本身太耗时。
  • 2)事件本身并不耗时,但需要等待别的地方返回耗时。
  • 3)UI线程本身已经拿不到CPU资源来执行事件。
2.1.1.1 耗时事件

就是把一些耗时业务逻辑直接写在了UI线程中,比如计算密集型的复杂计算,庞大的MD5计算,非对称RSA解密等。一般情况下,开发人员都不会犯这种错误,因为能够直接意识到计算量很大,本身就有警醒的作用。

2.1.1.2 耗时等待
  • 网络I/O 同步请求这种如果是在用以前比较老的网络库,比如URLConnection这种就需要开发人员自己来开启新的线程。开发者可能忘记开启子线程,又同时做了同步请求等待,导致卡顿的发生。但是现代网络库比如Okhttp,Retrofit已经帮我们准备好了线程池,一般不会再遇到

  • 磁盘I/O 文件,数据库一般的文件和数据库操作,大家可能都会自觉的在子线程中操作。但是值得一提的是SharedPreference的存储和读取,根据sp的设计,创建的时候会开启子线程把整个文件全部加载进内存,加载完毕再通知主线程,如果读取尚未结束,此时想获取某个key的值,主线程就必须等待加载完毕为止
    因此,如果你的sp文件比较大,那么会带来几个严重问题:
    a)第一次从sp中获取值的时候,有可能阻塞主线程,使界面卡顿、掉帧。
    b)解析sp的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿。
    c)这些key和value会永远存在于内存之中,不会被释放,占用大量内存。所以千万不要把庞大的key/value存在sp中,比如把复杂的json当value。
    另外对于sp的存储,commit是同步操作,要在子线程中使用。而apply虽然是在子线程执行的,但是无节制地apply也会造成卡顿,原因是每次有系统消息发生的时候(handleStopActivity,handlePauseActivity)都会去检查已经提交的apply写操作是否完成,如果没有完成则阻塞主线程。

2.1.2 分析

对于卡顿的分析手段,有很多工具可以使用,下面介绍几种。

1)TraceView相比之下,TraceView是分析卡顿的神兵利器,它不仅能看出每个方法消耗的时间、发生次数,并且可以进行排序,直接从最耗时的方法开始处优化。

2)ANR-WatchDog 其原理简单来说就是开启一个子线程,设置tick = interval,然后每隔一个interval(可设置)就往UI线程queue中扔一个runnable,若UI线程没卡顿,则interval时间内会取出此runnable执行,即重置tick,那么下一个interval循环时根据检测此tick是否被重置来判断是否有卡顿发生。如果有,则打印此时的各个线程运行时的stack trace(可设置只打印主线程),以帮助定位。

3)AndroidPerformanceMonitor 是国人开发的一个检测卡顿的开源库,原名是BlockCanary,可以设置卡顿检测时间,debug模式下检测到的卡顿可以通知展示(基本和LeakCanary一样),这个在开发自测时很有用。

利用系统在loop()方法里取出message前后进行了log打印这一特点,来重写Printer的println(String)方法,根据message处理前后的时间差,来判断是否发生了卡顿。

public static void loop() {
    
    
    ...

    for (;;) {
    
    
        ...

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
    
    
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
    
    
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        ...
    }
}

而且这个工具在卡顿发生时,收集的信息还比较丰富,包括基本信息,耗时信息,CPU信息,堆栈信息等。

4)ANR trace.txt而对于ANR,每当测试跑monkey一晚下来,ANR必是log的重点关注对象,若存在ANR,测试肯定会开jira贴上log给开发解决。对于trace.txt的分析,有几个基本的点是需要重点关注的:

  • a)具体的call stack指向的具体代码,是否是卡顿发生的原因。
  • b)是否有lock相关的关键字,代表可能发生死锁。
  • c)是否有iowait字样,是否在UI线程发生了网络或者磁盘I/O。
  • d)CPU使用率是否很高,很高表示要么自身有计算密集型任务发生,要么在其他地方有抢占CPU资源的任务。很低说明非耗时计算导致,可怀疑死锁和I/O耗时等待。

2.2 内存优化

Android 内存优化总结&实践

2.3 耗电优化

2.4 apk大小优化

2.5 io优化

3. Android系统应用

3.1 Launcher

3.2 SystemUI

3.3 Settings

猜你喜欢

转载自blog.csdn.net/dpjcn1990/article/details/108092455