likes
comments
collection
share

12W字;2022最新Android11位大厂面试专题(二)

作者站长头像
站长
· 阅读数 22

由于近期很多小伙伴开始面试了,所以在大量的刷题,也有很多问到我有没有一些大厂面试题或者常见的面试题,字节参考一下,于是乎花了一周时间整理出这份 《2022Android十一位大厂面试真题》 结合之前的 《腾讯Android开发笔记》 也算是双管齐下了!😃

一共50W字的文档,面试专题12W字只是一小部分,字数限制,分几篇更。

关注公众号:Android苦做舟

提前解锁 《整套50W字Android体系PDF》,让学习更贴近未来实战。

总共囊括

1.腾讯Android开发笔记(33W字)

2.2022最新Android十一位大厂面试专题(12W字)

3.音视频经典面试题(6W字)

4.Jetpack全家桶

5.Android 性能监控框架Matrix

6.JVM

7.车载应用开发

共十一模块,今天来更新第2专题阿里篇

二丶阿里篇

1. Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | Hook 点分析

①Hook点分析

在当前应用主进程中 , 在 Activity 中执行 startActivity 方法 , 然后调用 Instrumentation 中的 execStartActivity 方法 ;

然后在 ActivityManagerService 进程中 , 执行后续操作 ; ( 具体细节看上面的两篇博客 )

这里只需要研究 Activity 中执行 startActivity 方法的情况 , 因为插件化只涉及在 " 宿主 " 应用中 , 启动 " 插件 " 包中的 Activity 类 , 只需要考虑这一种情况即可 ;

Activity 的所有的生命周期函数 , 都与 Instrumentation 相关 , Instrumentation 会处理 Activity 实例化等操作 ;

②查看Instrumetation源码

查看 Instrumentation 相关代码 , 双 Shift 搜索界面中 , 选中 " Include non-project items " 选项 , 当前的编译版本是 28 , 因此这里选择 API 28 中的 Instrumentation.java 源码 ;

12W字;2022最新Android11位大厂面试专题(二)

③分析 Instrumentation.execStartActivity 方法

启动 Activity 时 , 调用的是下面的 Instrumentation.execStartActivity 方法 ;

public class Instrumentation {
    /**
     * Execute a startActivity call made by the application.  The default 
     * implementation takes care of updating any active {@link ActivityMonitor}
     * objects and dispatches this call to the system activity manager; you can
     * override this to watch for the application to start an activity, and 
     * modify what happens when it does. 
     *
     * <p>This method returns an {@link ActivityResult} object, which you can 
     * use when intercepting application calls to avoid performing the start 
     * activity action but still return the result the application is 
     * expecting.  To do this, override this method to catch the call to start 
     * activity so that it returns a new ActivityResult containing the results 
     * you would like the application to see, and don't call up to the super 
     * class.  Note that an application is only expecting a result if 
     * <var>requestCode</var> is >= 0.
     *
     * <p>This method throws {@link android.content.ActivityNotFoundException}
     * if there was no Activity found to run the given Intent.
     *
     * @param who The Context from which the activity is being started.
     * @param contextThread The main thread of the Context from which the activity
     *                      is being started.
     * @param token Internal token identifying to the system who is starting 
     *              the activity; may be null.
     * @param target Which activity is performing the start (and thus receiving 
     *               any result); may be null if this call is not being made
     *               from an activity.
     * @param intent The actual Intent to start.
     * @param requestCode Identifier for this request's result; less than zero 
     *                    if the caller is not expecting a result.
     * @param options Addition options.
     *
     * @return To force the return of a particular result, return an 
     *         ActivityResult object containing the desired data; otherwise
     *         return null.  The default implementation always returns null.
     *
     * @throws android.content.ActivityNotFoundException
     *
     * @see Activity#startActivity(Intent)
     * @see Activity#startActivityForResult(Intent, int)
     * @see Activity#startActivityFromChild
     *
     * {@hide}
     */
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    ActivityResult result = null;
                    if (am.ignoreMatchingSpecificIntents()) {
                        result = am.onStartActivity(intent);
                    }
                    if (result != null) {
                        am.mHits++;
                        return result;
                    } else if (am.match(who, null, intent)) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
                        }
                        break;
                    }
                }
            }
        }
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
}

在上述方法最后 , 调用了 AMS 的 startActivity 方法 , ActivityManager.getService().startActivity() 方法最终是 ActivityManagerService 执行的 ;

由于当前主线程与 ActivityManagerService 不再同一个进程中 , 因此需要使用 Binder 进行调用 ;

int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);

④分析 ActivityManager 中的源码

在 ActivityManager 中的 getService 方法 , 获取的

/**
     * @hide
     */
    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };

IActivityManager 是 Binder 的 Proxy , Binder 下存在 Stub 和 Proxy 两个内部类 , Binder 生成的 Java 类时内部生成上述 Stub 和 Proxy 两个内部类 ;

反射的时候 , 反射

final IActivityManager am = IActivityManager.Stub.asInterface(b);

对象 , 最终调用 startActivity 的是 IActivityManager , 使用占坑的 Activity 隐瞒 IActivityManager , 实际上启动我们从插件包中加载的 Activity ;

Hook 点就是 android.app.ActivityManager 的 private static final Singleton IActivityManagerSingleton成员 ;

2.Android中Handler处理Runnable任务的常见方法

  • post:立即启动Runnable任务
  • postDelayed:延迟若干时间后启动Runnable任务。
  • postAtTime:在指定时间启动Runnbale任务。
  • removeCallbacks:移除指定的Runnable任务。

3.为什么要有handler?

①为什么要有handler?

主要目的是要解决线程切换问题,handler里的Message机制解决了线程间通信;

②为什么有队列MessageQueue

MessageQueue是一个单向链表,next()调用nativePollOnce->lunx的epoll_wait()等待实现阻塞时队列;

  • 在单线程中一次只能执行一句代码
  • 假如发送了一个大消息A
  • 处理这个大的消息A
  • 但是处理的太慢了
  • 从而导致其他后续要发送的消息发不出去
  • 因为单线程阻塞到了第3步处理那个消息A的地方

队列的出现解决了"处理消息"阻塞到"发送消息"的问题;

队列是生产者消费者模式;

而要使用队列需要至少两个线程、和一个死循环;

  • 一个线程负责生产消息;
  • 一个线程消费消息;
  • 死循环需要取出放入队列里的消息;

③为什么有Looper?

为了循环取出队列里的消息;

一个线程有几个Looper,为什么不会有多个?

一个线程一个Looper,放在ThreadLocalMap中;

假如Looper对象由Handler创建,每创建一个Handler就有一个Looper,那么调用Looper.loop()时开启死循环;在外边调用Looper的地方就会阻塞;

12W字;2022最新Android11位大厂面试专题(二)

④主线程中Looper的死循环为什么没有导致系统卡死?

  • 我们的UI线程主线程其实是ActivityThread线程,而一个线程只会有一个Looper;
  • ActivityThread.java的main函数是一个APP进程的入口,如果不卡死,main函数执行完则整个应用进程就会退出;
  • android是以事件为驱动的操作系统,当有事件来时,就去做对应的处理,没有时就显示静态界面;

获取当前线程:Thread.currentThread();

ThreadLocalMap:类似于HashMap;

每个Thread对象都有一个对应的ThreadLocalMap;

12W字;2022最新Android11位大厂面试专题(二)

Looper.prepare()时,存入Looper,存Looper时ThreadLocalMap的key为ThreadLocalvalueLooper

12W字;2022最新Android11位大厂面试专题(二)

12W字;2022最新Android11位大厂面试专题(二)

内存抖动根本的解决方式是复用;

handler.obtainMessage();

  • 从Looper的回收池中取Message;

  • Message是一个单向链表,Message不是一个单纯的对象,而是一个链表集合

  • 最大长度固定50个

    Linux函数: epoll_create:App注册进红黑树中,拿到一个事件fd的值; epoll_ctl:注册事件类型,监听fd是否改变(Linux中事件都会被写入文件中,如触摸屏幕事件会写入到:dev/input/event0文件中),fd有改变时唤醒epoll_wait; epoll_wait:有事件时就分发,没事件就阻塞

12W字;2022最新Android11位大厂面试专题(二)

⑤总结:

handler如何做的线程切换的? 首先Handler的使用步骤:

  • 调用Looper.prepare();

  • 创建Handler对象;

  • 调用Looper.Loop()方法。

  • 线程中发送消息。

在第一步时,创建一个Looper,并放到当前线程的变量threadLocals中;threadLocals是一个map,key为ThreadLocal对象本身,value为Looper;在Looper.loop()时取出;

12W字;2022最新Android11位大厂面试专题(二)

第二步,用户在当前线程(可能是子线程)创建Handler对象;

第三步,Looper.loop()一直在死循环,Looper.loop()这句代码下面的代码是不会被调用的,调用Looper.loop()函数时,先从当前线程的map变量中取出Looper,再从Looper中拿到队列MessageQueue,for循环中不断从队列中取出消息

12W字;2022最新Android11位大厂面试专题(二)

第四步,在其他线程调用handelr发送消息时,Message里有个target,就是发送消息的handler;

12W字;2022最新Android11位大厂面试专题(二)

Looper.loop()时,队列中取到消息时,调用msg.target.dispatchMessage(msg);其实就是handler对象.dispatchMessage(msg);

12W字;2022最新Android11位大厂面试专题(二)

所以不论在哪个线程调用发送消息,都会调用到handler自己分发消息;而handler所处的线程是创建时的“当前线程”,所以处理时也就回到了“当前线程”;实现了线程切换,和线程通信;

⑥Looper的死循环为什么不会让主线程卡死(或ANR)?

简单版:

  • 我们的UI线程主线程其实是ActivityThread所在的线程,而一个线程只会有一个Looper;
  • ActivityThread.java的main函数是一个APP进程的入口,如果不一直循环,则在main函数执行完最后一行代码后整个应用进程就会退出;
  • android是以事件为驱动的操作系统,当有事件来时,就去做对应的处理,没有时就显示静态界面;
  • ANR发生条件是: Activity:5 秒。应用在 5 秒内未响应用户的输入事件(如按键或者触摸) BroadCastReceiver :10 秒。BroadcastReceiver 未在 10 秒内完成相关的处理 Service:20 秒(均为前台)。Service 在20 秒内无法处理完成
  • 如果Handler收到以上三个相应事件在规定时间内完成了,则移除消息,不会ANR;若没完成则会超时处理,弹出ANR对话框;

详细:

  1. App进程的入口为ActivityThread.java的main()函数,注意ActivityThread不是一个线程;
  2. 应用的ui主线程实际是调用ActivityThread.java的main()函数执行时所在的线程,而这个线程对我们不可见,但是这就是主线程;参考:
  3. ActivityThread.javamain()函数中,会调用Looper.prepareMainLooper();
  4. Looper.prepareMainLooper()会创建一个Looper并放到当前线程(主线程)的变量threadLocals中进行绑定,threadLocals是一个ThreadLocal.ThreadLocalMap;
  5. ActivityThread.javamain()函数结尾,开启Looper.loop()进行死循环,不让main函数结束,从而让App进程不会结束;
  6. Android系统是以事件作为驱动的操作系统,当有事件来时,就去做对应处理,没有事件时,就显示当前界面,不做其他多余操作(浪费资源);
  7. Looper.loop()的死循环中,不仅要取用户发的事件,还要取系统内核发的事件(如屏幕亮度改变等等);
  8. 在调用Looper.loop()时,从MessageQueue.next()中获取事件,若没有则阻塞,有则分发;
  9. MessageQueue其实不是一个队列,用epoll机制实现了阻塞;
  10. Looper.prepareMainLooper()时,调用c++函数epoll_create()会将App注册进epoll机制的红黑树中得到fd的值,epoll_ctl()给每个App注册事件类型并监听fd值是否改变,fd有改变时唤醒epoll_wait;
  11. epoll_wait()有事件时就分发,没事件就阻塞

⑦子线程的Looper和子线程Looper有什么不同?

子线程Looper是可以退出的,主线程不行;

4.求二叉树中两个节点之间的最大距离。

例如这样一棵二叉树:

12W字;2022最新Android11位大厂面试专题(二)

什么叫做二叉树结点间的最大距离呢?例如从结点a出发,可以向上走或向下走,但沿途的结点只能经过一次,到达结点b时路径上经过的结点个数叫做a到b的距离,因此,二叉树任意两个结点间都有距离,那么也就存在一个最大距离。

上图二叉树中B到C的距离就是3.最大距离容易得出是6,为G到F的距离。

所谓最大距离,可以分为三种情况,最大距离就在根结点的左子树上,或者右子树上,这两种情况下,根结点都是不参与进去的。所谓不参与进去就是计算最大距离路径上结点时不把根结点也算进去。那么第三种情况就包含了根结点,也就是说求解最大距离时的路径需要经过根结点,也就是上图二叉树中的这种情况。因此,为了实现程序,我们可以用递归的方法:

  public class MaxDistance {
      private static class Node {
          public char value;
          public Node left;
          public Node right;
  
          public Node(char value, Node left, Node right) {
              this.left = left;
              this.right = right;
             this.value = value;
         }
     }
 
     public static class Info {
         public int maxDistance;
         public int height;
 
         public Info(int dis, int h) {
             maxDistance = dis;
             height = h;
         }
     }
 
     private static Info process(Node node) {
         if (node == null) {
             return new Info(0, 0);
         }
         Info leftInfo = process(node.left);
         Info rightInfo = process(node.right);
         int d1 = leftInfo.maxDistance;
         int d2 = rightInfo.maxDistance;
         int d3 = leftInfo.height + 1 + rightInfo.height;
         int maxDistance = Math.max(d1, Math.max(d2, d3));
         int height = Math.max(leftInfo.height, rightInfo.height) + 1;
         return new Info(maxDistance, height);
     }
 
     public static int getMaxDistance(Node head) {
         return process(head).maxDistance;
     }
 
     public static void main(String[] args) {
         Node nodeG = new Node('B', null, null);
         Node nodeD = new Node('B', null, null);
         Node nodeE = new Node('B', nodeG, null);
         Node nodeF = new Node('B', null, null);
         Node nodeB = new Node('B', nodeD, nodeE);
         Node nodeC = new Node('B', null, nodeF);
         Node nodeA = new Node('A', nodeB, nodeC);
        int distance = getMaxDistance(nodeA);
         System.out.println(distance);
    }
 }

这其中的核心方法是这个:

private static Info process(Node node) {
        if (node == null) {
            return new Info(0, 0);
        }
        Info leftInfo = process(node.left);
        Info rightInfo = process(node.right);
        int d1 = leftInfo.maxDistance;
        int d2 = rightInfo.maxDistance;
        int d3 = leftInfo.height + 1 + rightInfo.height;
        int maxDistance = Math.max(d1, Math.max(d2, d3));
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        return new Info(maxDistance, height);
    }

我需要取得某一个结点的左子树和右子树的情况,当然对应于第三种情况,两边树高再加上结点本身就是距离,这三个数比较一下大小,很容易就能算出最大距离,同时,还要记录算上当前结点的树高,全部计算完毕后,把这个对象返回出去即可。

运行也无问题:

12W字;2022最新Android11位大厂面试专题(二)

5.谈谈你对Binder的认识?

①为什么要用Binder?

  • Android系统内核是Linux内核
  • Linux内核进程通信有:管道、内存共享、Socket、File;
  • 对比:

12W字;2022最新Android11位大厂面试专题(二)

Binder的一次拷贝发生在用户空间拷贝到内核空间;

用户空间: App进程运行的内存空间;

内核空间: 系统驱动、和硬件相关的代码运行的内存空间,也就是进程ID为0的进程运行的空间;

程序局部性原则: 只加载少量代码;应用没有运行的代码放在磁盘中,运行时高速缓冲区进行加载要运行的代码;默认一次加载一个页(4K),若不够4K就用0补齐;

MMU:内存管理单元;

给CPU提供虚拟地址;

当对变量操作赋值时:

  • CPU拿着虚拟地址和值给到MMU
  • MMU用虚拟地址匹配到物理地址,MMU去物理内存中进行赋值;

物理地址: 物理内存的实际地址,并不是磁盘;

虚拟地址: MMU根据物理内存的实际地址翻译出的虚拟地址;提供给CPU使用;

12W字;2022最新Android11位大厂面试专题(二)

12W字;2022最新Android11位大厂面试专题(二)

页命中:CPU读取变量时,MMU在物理内存的页表中找到了这个地址;

页未命中:CPU读取变量时,MMU在物理内存的页表中没有找到了这个地址,此时会触发MMU去磁盘读取变量并存到物理内存中;

普通的二次拷贝:

应用A拷贝到服务端:coay_from_user

从服务端拷贝到应用B:coay_to_user

mmap():

  • 在物理内存中开辟一段固定大小的内存空间
  • 将磁盘文件与物理内存进行映射(理解为绑定)
  • MMU将物理内存地址转换为虚拟地址给到CPU(虚拟地址映射物理内存)

共享内存进程通信:

  • 进程A调用mmap()函数会在内核空间中虚拟地址和一块同样大小的物理内存,将两者进行映射
  • 得到一个虚拟地址
  • 进程B调用mmap()函数,传参和步骤1一样的话,就会得到一个和步骤2相同的虚拟地址
  • 进程A和进程B都可以用同一虚拟地址对同一块映射内存进行操作
  • 进程A和进程B就实现了通信
  • 没有发生拷贝,共享一块内存,不安全

②Binder通信原理:

角色:Server端A、Client端B、Binder驱动、内核空间、物理内存

  • Binder驱动在物理内存中开辟一块固定大小(1M-8K)的物理内存w,与内核空间的虚拟地址x进行映射得到
  • A的用户空间的虚拟地址ax和物理内存w进行映射
  • 此时内核空间虚拟地址x和物理内存w已经进行了映射,物理内存w和Server端A的用户空间虚拟地址ax进行了映射:也就是 内核空间的虚拟地址x = 物理内存w = Server端A的用户空间虚拟地址ax
  • B发送请求:将数据按照binder协议进行打包给到Binder驱动,Binder驱动调用coay_from_user()将数据拷贝到内核空间的虚拟地址x
  • 因步骤3中的三块区域进行了映射
  • Server端A就得到了Client端B发送的数据
  • 通过内存映射关系,只发生了一次拷贝

12W字;2022最新Android11位大厂面试专题(二)

Activity跳转时,最多携带1M-8k(1兆减去8K)的数据量;

真实数据大小为:1M内存-两页的请求头数据=1M-8K;

应用A直接将数据拷贝到应用B的物理内存空间中,数据量不能超过1M-8K;拷贝次数少了一次,少了从服务端拷贝到用户;


IPC通信机制:

  • 服务注册
  • 服务发现
  • 服务调用

以下为简单的主进程和子进程通信:

1、服务注册: 缓存中心中有三张表(暂时理解为三个HashMap,Binder用的是native的红黑树):

  • 第一种:放key :String - value:类的Class;
  • 第二种:放key :Class的类名 - value:类的方法集合;
  • 第三种:放key :Class的类名 - value:类的对象;

类的方法集合:key-value;

key:方法签名:“方法名” 有参数时用 “方法名-参数类型-参数类型-参数类型......”;

value: 方法本身;

注册后,服务若没被调用则一直处于沉默状态,不会占用内存,这种情况只是指用户进程里自己创建的服务,不适用于AMS这种;

2、服务发现: 当被查询到时,要被初始化;

  • 客户端B通过发送信息到服务端A
  • 服务端解析消息,反序列化
  • 通过反射得到消息里的类名,方法,从注册时的第一种、第二种表里找到Class,若对象没初始化则初始化对象,并将对象添加到第三种的表里;

3、服务调用:

  • 使用了动态代理
  • 客户端在服务发现时,拿到对象(其实是代理)
  • 客户端调用对象方法
  • 代理发送序列化数据到服务端A
  • 服务端A解析消息,反序列化,得到方法进行处理,得到序列化数据结果
  • 将序列化结果写入到客户端进程的容器中;
  • 回调给客户端

AIDL: BpBinder:数据发送角色 BbBinder:数据接收角色

12W字;2022最新Android11位大厂面试专题(二)

编译器生成的AIDL的java接口.Stub.proxy.transact()为数据发送处;

发送的数据包含:数据+方法code+方法参数等等;

  • 发送时调用了Linux的驱动
  • 调用copy_from_user()拷贝用户发送的数据到内核空间
  • 拷贝成功后又进行了一次请求头的拷贝:copy_from_user()
  • 也就是把一次的数据分为两次拷贝

请求头:包含了目的进程、大小等等参数,这些参数占了8K

编译器生成的AIDL的java接口.Stub.onTransact()为数据接收处;

③Binder中的IPC机制:

  • 每个App进程启动时会在内核空间中映射一块1M-8K的内存
  • 服务端A的服务注册到ServiceManager中:服务注册
  • 客户端B想要调用服务端A的服务,就去请求ServiceManager
  • ServiceManager去让服务端A实例化服务:服务发现
  • 返回一个用来发送数据的对象BpBinder给到客户端B
  • 客户端B通过BpBinder发送数据到服务端A的内核的映射区域(传参时客户端会传一个reply序列化对象,在底层会将这个地址一层一层往下传,直至传到回调客户端):这里发生了一次通信copy_from_user:服务调用
  • 服务端A通过BBBinder得到数据并处理数据
  • 服务端唤醒客户端等待的线程;将返回结果写入到客户端发送请求时传的一个reply容器地址中,调用onTransact返回;
  • 客户端在onTransac中得到数据;通信结束;

ServiceManager维持了Binder这套通信框架;

6.动态代理实现

设计模式之动态代理

什么是动态代理网上已经讲了很多了,这里我就没必要讲了,只贴一个简单概念出来

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:

  • 代理模式会造成系统设计中类的数量增加
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

Jdk动态代理实现的源码分析 实现: jdk动态代理的实现为:实现java.lang.reflect.InvocationHandler接口 例子:

class ProxyObject implements InvocationHandler {
    private final Object target;

    public ProxyObject(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Objects.requireNonNull(target);
        System.out.println("代理开始前");
        Object ret = method.invoke(target, args);
        System.out.println("代理结束");
        return ret;
    }
}

然后使用java.lang.reflect.Proxy的newProxyInstance方法

//调用jdk的api生成代理类
    public static <T> T getProxy(Class<T> ret, Class<?>[] interfaces, InvocationHandler handler) {
        Object o = Proxy.newProxyInstance(handler.getClass().getClassLoader(), interfaces, handler);
        return (T) o;
    }

所以要明白jdk动态代理实现,我们的切入点就是newProxyInstance方法 这里贴出该方法中重要的代码段:

//newProxyInstance方法
...
//Look up or generate the designated proxy class.
Class<?> cl = getProxyClass0(loader, intfs);
....
//获取生成代理类的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
...
//反射调用代理类的构造方法实例化
return cons.newInstance(new Object[]{h});

从上面的代码可以看出来,我们关心的是getProxyClass0这个方法,怎么生成的代理类,我们继续往下看:

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
      	...
        //可以看到,代理类是有缓存的,通过缓存的key值是对应的类加载器
        return proxyClassCache.get(loader, interfaces);
}
//缓存的get方法
public V get(K key, P parameter) {
       	...
        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
    	//这里的apply方法生成subKey
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));	
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                //get方法获取对应的代理对象
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
                if (factory == null) {
                	factory = new Factory(key, parameter, subKey, valuesMap);
            	}
				...
                //将构建的factor赋值给supplier
                supplier = factory;
            }
		...
       }
    ...
 } 
//Factory类的声明
  private final class Factory implements Supplier<V> {
		...
        @Override
        public synchronized V get() { // serialize access
            // re-check
            //主要部分
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            ...
        }
      ...
  }
//proxyClassCache的声明
 /**
     * a cache of proxy classes
     */
 private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

从上面的代码中可以看出,代理类的生成是在ProxyClassFactory的apply方法中

 //ProxyClassFactory的apply方法
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

          	...
            /*
             * Generate the specified proxy class.
             */
            //生成代理类
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

但是ProxyGenerator,generateProxyClass方法的源码在sun.misc包下面,没有找到源码。 jdk动态代理大概流程 但是jdk整个动态代理的大概流程,通过上面的分析,应该已经知晓了整理一下就是 1.根据传入的接口生成一个代理类(这个代理类有一个参数为InvocationHandler的构造方法) 2.将InvocationHandler的实现类传入到代理类的构造方法中,并实例化

代理类数据结构 根据这个我们可以大概推敲一下代理类的数据结构,这里贴出我所认为的结构(基于Human接口):

//接口Human
interface Human {
    void say(String args);
}
class Chinese implements Human {
    @Override
    public void say(String word) {
        System.out.println("哈哈,中国:" + word);
    }
}
//我所认为的jdk为human接口生成的代理类
class SimulateProxy implements Human {
    private final InvocationHandler handler;

    public SimulateProxy(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void say(String word) {
        Objects.requireNonNull(handler);
        try {
            Method say = Human.class.getDeclaredMethod("say", String.class);
            Object invoke = handler.invoke(this, say, new Object[]{word});
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}
//测试猜测
class ProxyObject implements InvocationHandler {
    private final Object target;

    public ProxyObject(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Objects.requireNonNull(target);
        System.out.println("代理开始前");
        Object ret = method.invoke(target, args);
        System.out.println("代理结束");
        return ret;
    }
}
public static void main(String[] args) {
    Chinese chinese = new Chinese();
    Class<?>[] classes = new Class<?>[]{Human.class};
   	InvocationHandler handler = new ProxyObject(chinese);
    SimulateProxy proxy0 = new SimulateProxy(handler);
    proxy0.say("推测的结构")
}
/////////////////////
// 运行结果:
// 代理开始前
// 哈哈,中国:推测的结构
// 代理结束

好了,通过上面的描述,我们清楚了动态代理生成代理类的结构,所以现在要自己实现类似jdk的动态代理就剩下一个问题,怎么根据接口来生成对应的代理类。这里会用到一个java字节码生成框架javassist

自己实现动态代理类似于Jdk的动态代理 思路: 根据接口生成动态代理的类的思路如下: 1.先利用反射获取接口信息,主要是方法签名(方法名称,方法返回类型,方法入参类型) 2.利用javassist根据接口信息生成对应的字节码 3.返回该字节码

上面的代理实现了单个接口的动态代理,并且兼容Jdk的InvocationHandler接口,如果需要实现多接口的代理,也简单,只需要将多个接口的方法信息传入上面的生成函数即可。

7.ASM 的原理

AMS(ActivityManagerService) 在SystemServer的进程中,是SystemServer中的一个对象;

①作用:

  • 管理activity的生命周期

  • 启动activity

  • 与PMS进行交互

    Activity->AMS:

  • 调用activity.startActivity()

  • 通过ActivityManage.getService("activity")得到AMS的BpBinder;

  • 通过BpBinder发送请求,调用AMS的startActivity()

    AMS->PMS:

  • AMS和PMS都在SystemServer进程中,都是SystemServer中一个对象

  • 通过包名和PMS里的缓存mPackage查询到App对应的Package

  • 使用activity的类名通过PMS里的内部类PackageManagerInternalImpl查询到activity对应的包装类ResolveInfo; ps:ResolveInfo这个javabean里有activityInfo、ServiceInfo等变量,查询啥就给哪个变量赋值,再返回ResolveInfo;

  • 得到ResolveInfo里的activityInfo;

  • activityInfo返回给App进程的ActivityThread;`

  • ActivityThread中发送事件

  • ActivityThread中的Handler对象mH收到159事件,处理

  • 通过反射创建Activity对象

  • 将Activity对象放到activtes启动记录中

12W字;2022最新Android11位大厂面试专题(二)

②ActivityThread

  • 每个应用有一个ActivityThread;是应用的入口;

  • 在APP进程中

  • 是AMS的缓存中心

  • ActivityThread中的List activtes放了activity的启动记录

③ActivityThread中重要的对象:

  • ApplicationThread:AMS回调给ActivityThread数据的桥梁
  • mInstrumentation:管理Application和Activity的生命周期(及创建)
  • mH:Handler,处理ApplicationThread里各种回调函数发送的各种消息

④点击桌面App图标发生了什么?

  1. 点击的APP图标是在单独的Luancher进程,是一个系统App进程
  2. Luancher进程请求SystemServer进程中的AMS去创建应用的根Activity(AndroidMnifest.xml中initen-fifter为Luanche的activity)
  3. AMS通过包名让PMS查询到相关应用信息,得到应用的Package;
  4. AMS创建activity栈,根据Package拿到根activity的配置节点信息,放到栈中,此时栈中只有一个根activity的配置节点信息,也就是在栈顶;(此处的栈不是应用层的栈,这个栈只是用来放activity节点信息的)
  5. AMS请求zygote进程创建App进程;zygote进程比较特殊, 使用Socket通信,而不是binder;zygote是所有应用的孵化器,zygote进程挂掉时,手机会自动重启;
  6. zygote进程去fork出App进程;
  7. APP进程中的主线程调用ActivityThread.main()静态函数,main中创建 ActivityThread对象
  8. 接着在ActivityThread.attch()中创建了一个ApplicationThread对象,作为和AMS通信时,返回结果的桥梁;
  9. App进程通过AMS的binder调用attachApplication(thread)请求AMS获取应用对应的Applaction和栈顶中activity节点信息(步骤4),此时给AMS传过去了一个thread,这个thread就是ApplicationThread
  10. AMS将从PMS查到的application节点数据序列化后,调用thread.bindApplaction(data数据...)传给ActivityThread; (此时代码还会继续往下执行,去获取栈顶activity的节点信息)
  11. ActivityThread调用sendMessage发送消息BIND_APPLICATION(110)给Handler,Handler调用handleBindApplication(data)
  12. 通过反射实例化Instrumentation对象:负责application和activity的生命周期的管理
  13. 通过Instrumentation对象反射实例化new Applaction对象app
  14. 调用Instrumentation.callApplactionOnCreate(app)
  15. 执行Applaction.onCreate()
  16. 步骤10中AMS继续向下执行查找activity,AMS将查到的栈顶根Activity(LaunchActivity )信息封装到一个事务ClientTransaction中,提交事务并执行,在执行中,调用thread.scheduleTransaction(事务数据);(thread为ActivityThread中的ApplicationThread
  17. ApplicationThread回调scheduleTransaction函数中,发送`EXECUTE_TRANSACTION(159)消息
  18. Handler处理EXECUTE_TRANSACTION消息,从事务数据中取出LaunchActivity信息,并调用hanldeLaunchActivity(activity数据)
  19. 通过Instrumentation对象反射实例化newActivity()出对象activity
  20. 执行activity.attach(),在attach中创建WMS的桥接代理类;(绘制流程会用到)
  21. 通过Instrumentation调用callActivityOnCreate(activity)
  22. 执行Activty.onCreate();
  23. 至此启动页根Activity启动完成;

下图中4-5中少了上面7-23的步骤:

7-15创建并启动了Application;

16-22创建并启动了Activity;

12W字;2022最新Android11位大厂面试专题(二)

⑤应用内activity与activity的跳转是跨进程通信,还是同一个进程内通信?

是跨进程通信;

跳转流程参考上面的:省去了application的创建过程;

步骤3 +步骤16-23;

8.内存泄漏常见场景以及解决方案

①资源性对象未关闭

对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap 等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。

②注册对象未注销

例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。

③类的静态变量持有大数据对象

尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。

④单例造成的内存泄漏

优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封 装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。

⑤非静态内部类的静态实例

该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源 不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如 果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置 空让GC可以回收,否则还是会内存泄漏。

⑥Handler临时性内存泄漏

Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的, 则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息, 当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message 持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回 收,引发内存泄漏。解决方案如下所示: 1、使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这 样在回收时,也可以回收Handler持有的对象。 2、在Activity的Destroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中 有待处理的消息需要处理 (Handler那篇有讲)

⑦容器中的对象没清理造成的内存泄漏

在退出程序之前,将集合里的东西clear,然后置为null,再退出程序

⑧WebView

WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为 WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业 务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

⑨使用ListView时造成的内存泄漏

在构造Adapter时,使用缓存的convertView。

⑩Bitmap

80%的内存泄露都是Bitmap造成的,Bitmap有Recycle()方法,不用时要及时回收。但是如果遇到要用Bitmap直接用Glide就完事了,自己写10有8.9得出错。

9.touch 事件源码问题。

1.ViewRootImpl接收Event

  • ViewRootImpl的WindowInputEventReceiver中会接收系统传递的按键,执行onInputEvent方

  • onInputEvent方法执行enqueueInputEvent,再执行doProcessInputEvents或者scheduleProcessInputEvents,最终执行到deliverInputEvent

  • deliverInputEvent中通过InputStage向对应的输入目标发送按键,安卓view对应的stage是ViewPostImeInputStage

  • ViewPostImeInputStage中执行processPointerEvent,最终发到mView.dispatchPointerEvent(event),此处的mView就是根view,对于Activity来说就是Activity的根view(DecorView)

    		private int processPointerEvent(QueuedInputEvent q) {
                final MotionEvent event = (MotionEvent)q.mEvent;
                mAttachInfo.mUnbufferedDispatchRequested = false;
                mAttachInfo.mHandlingPointerEvent = true;
                boolean handled = mView.dispatchPointerEvent(event);
                maybeUpdatePointerIcon(event);
                maybeUpdateTooltip(event);
                mAttachInfo.mHandlingPointerEvent = false;
                if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                    mUnbufferedInputDispatch = true;
                    if (mConsumeBatchedInputScheduled) {
                        scheduleConsumeBatchedInputImmediately();
                    }
                }
                return handled ? FINISH_HANDLED : FORWARD;
            }
    

2.Event事件从DecodeView传递到view的dispatchTouchEvent

DecodeView的dispatchPointerEvent事件

  //dispatchPointerEvent 区分是touch事件还是鼠标事件
  public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

DecodeView的dispatchTouchEvent

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
       	//判断是否是window对象,区分事件分析,DecodeView会走cb.dispatchTouchEvent(ev),最终执行到			   activity的dispatchTouchEvent
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

Activity的dispatchTouchEvent,会执行getWindow().superDispatchTouchEvent(ev)。getWindow是PhoneWindow对象,PhoneWindow的superDispatchTouchEvent执行的是mDecor.superDispatchTouchEvent(event),而DecodeView的此方法执行调用super.dispatchTouchEvent(event),最终执行到View的dispatchTouchEvent方法

  //Activity的dispatchTouchEvent
  public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

3.view中Event的处理

viewGroup分发

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            //如果是DOWN事件,会先清除掉touchtarget对象以及touch状态
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            //如果当前是DOWN事件或者已经有view处理了事件,则判断是否当前view拦截事件
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                 //是否禁止拦截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                	//判断是否拦截按键
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
            //是否允许多点触控
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                    && !isMouseEvent;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
                // If the event is targeting accessibility focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x =
                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                        final float y =
                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //遍历子元素,一般是底层的优先
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                              //是否可以接收事件,可见或者有动画,是否在点击区域内
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }
							//判断当前子view是否在触摸记录链表中,如果存在直接break,此处需要注意,按键有可能不会往下层的子view分发,因为已经查到了可处理按键的view链
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            //将按键向子view分发,判断子view是否处理按键
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //如果子view处理了按键,则将子view加入到touchtarget链表中,单点触控中,链表长度为1,多点触控中,链表长度可能大于1
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                //记录按键已经处理完成
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
					//此处需注意,如果没有newTouchTarget 并且当前view的mFirstTouchTarget 不为空,则将按键交给mFirstTouchTarget处理,比如A包含B,C,B包含D,第一个手指在D按下,第二个手指在B中不包含D的位置按下,此时按键会发给D,而不会往C分发,就是因为再B中,newTouchTarget 未空,而mFirstTouchTarget 指向了D
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {//当前View处理按键
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {//分发到target,如果是多指按下,target的next不为空,会顺序往后分发
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

dispatchTransformedTouchEvent方法

   /**
     * Transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        //一个按键包含的所有触摸点
        final int oldPointerIdBits = event.getPointerIdBits();
        //跟当前需要的触摸点进行合并
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
             //排除不需要的触摸点
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
           	//分发按键
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

View中的按键处理

 public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //执行OnTouchListener
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
 			//执行onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

10.Android自定义崩溃收集器捕获java层和native层崩溃异常日志

① 在 Applicaiton中进行初始化崩溃收集器

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //初始化崩溃收集器
        CollectCrashUtils.initColleteCrash();
    }
}

② 收集java层崩溃和native层崩溃

public class CollectCrashUtils {
    public static void initColleteCrash() {
        //初始化Handler,收集java层崩溃
        MyJavaCrashHandler handler = new MyJavaCrashHandler();
        Thread.setDefaultUncaughtExceptionHandler(handler);

        //收集native层崩溃
        File file = new File("sdcard/Crashlog");
        if (!file.exists()) {
            file.mkdirs();
        }
        NativeBreakpad.init(file.getAbsolutePath());
    }
}

③ native层的崩溃收集可以使用编译好的breakpad.so。

④java层崩溃实现Thread.UncaughtExceptionHandler接口进行收集

public class MyJavaCrashHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Log.e("程序出现异常了", "Thread = " + t.getName() + "\nThrowable = " + e.getMessage());
        String stackTraceInfo = getStackTraceInfo(e);
        Log.e("stackTraceInfo", stackTraceInfo);
        saveThrowableMessage(stackTraceInfo);
    }

    /**
     * 获取错误的信息
     *
     * @param throwable
     * @return
     */
    private String getStackTraceInfo(final Throwable throwable) {
        PrintWriter pw = null;
        Writer writer = new StringWriter();
        try {
            pw = new PrintWriter(writer);
            throwable.printStackTrace(pw);
        } catch (Exception e) {
            return "";
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
        return writer.toString();
    }

    private String logFilePath = "sdcard/Crashlog";

    private void saveThrowableMessage(String errorMessage) {
        if (TextUtils.isEmpty(errorMessage)) {
            return;
        }
        File file = new File(logFilePath);
        if (!file.exists()) {
            boolean mkdirs = file.mkdirs();
            if (mkdirs) {
                writeStringToFile(errorMessage, file);
            }
        } else {
            writeStringToFile(errorMessage, file);
        }
    }

    private void writeStringToFile(final String errorMessage, final File file) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                FileOutputStream outputStream = null;
                try {
                    ByteArrayInputStream inputStream = new ByteArrayInputStream(errorMessage.getBytes());
                    outputStream = new FileOutputStream(new File(file, System.currentTimeMillis() + ".txt"));
                    int len = 0;
                    byte[] bytes = new byte[1024];
                    while ((len = inputStream.read(bytes)) != -1) {
                        outputStream.write(bytes, 0, len);
                    }
                    outputStream.flush();
                    Log.e("程序出异常了", "写入本地文件成功:" + file.getAbsolutePath());
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (outputStream != null) {
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }

}

11.如果 int 的值大于了 3 需要提示。

在C语言中,int是用两个字节表示的,范围是-32768到+32767,超过这个范围的就不能表示了,只能用long int表示了

12.介绍下 flutter 的启动流程

Flutter是怎么启动起来的,是在Android的Activity的启动之后吗?等等这样的问题,在这个文章中将被解答。

①从MainActivity开始

新创建一个Flutter项目,在清单文件中默认被启动的Activity是MainActivity,而MainActivity继承的是FlutterActivity。那么问题好像简单了,我们分析一下FlutterActivity,下面是MainActivity的代码。

public class MainActivity extends FlutterActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 GeneratedPluginRegistrant.registerWith(this);
 }
}

上面的源码很简单,有两个点需要关注,第一个就是在MainActivity.onCreate()中调用了GeneratedPluginRegistrant.registerWith()这个方法,第二个是MainActivity继承自FlutterActivity,我们对这两个关注点依次进行分析。

GeneratedPluginRegistrant.registerWith()分析

我们查看GeneratedPluginRegistrant,发现这个类是Android Studio自动生成的,并且不建议修改,这个类也非常简单,下面是GeneratedPluginRegistrant的代码。

public final class GeneratedPluginRegistrant {
 public static void registerWith(PluginRegistry registry) {
 if (alreadyRegisteredWith(registry)) {
 return;
 }
 }
 
 private static boolean alreadyRegisteredWith(PluginRegistry registry) {
 final String key = GeneratedPluginRegistrant.class.getCanonicalName();
 if (registry.hasPlugin(key)) {
 return true;
 }
 registry.registrarFor(key);
 return false;
 }
}

如果PluginRegistry已经包含了GeneratedPluginRegistrant就直接返回true,如没有就调用PluginRegistry.registrarFor()进行注册。

我们分析一下PluginRegistry,看看是怎么注册的,发现PluginRegistry是一个接口,下面是PluginRegistry的代码。

public interface PluginRegistry {
 Registrar registrarFor(String pluginKey);
}

PluginRegistry的实现是谁呢?是FlutterActivity,下面开发分析FlutterActivity,暂时看FlutterActivity比较重要,因为这个类是MainActivity的父类,还是PluginRegistry的具体实现类。

③FlutterActivity分析

下面是FlutterActivity的代码。

public class FlutterActivity extends Activity implements FlutterView.Provider, PluginRegistry, ViewFactory {
 private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
 
 private final FlutterActivityEvents eventDelegate = delegate;
 private final FlutterView.Provider viewProvider = delegate;
 private final PluginRegistry pluginRegistry = delegate;
 
 @Override
 public final boolean hasPlugin(String key) {
 return pluginRegistry.hasPlugin(key);
 }
 
 @Override
 public final Registrar registrarFor(String pluginKey) {
 return pluginRegistry.registrarFor(pluginKey);
 }
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 eventDelegate.onCreate(savedInstanceState);
 }
 
 @Override
 protected void onDestroy() {
 eventDelegate.onDestroy();
 super.onDestroy();
 }
 
 	@Override
 protected void onStop() {
 eventDelegate.onStop();
 super.onStop();
 }
 //省略了一些代码
}

从上面的代码中可以看出来,FlutterActivity是继承Activity和实现了PluginRegistry。分析一下onCreate,onStop,onDestroy这些生命周期方法被FlutterActivity.eventDelegate代理了,这个时候我们明白了,FlutterActivity就是一个空壳,真正实现是代理类FlutterActivityDelegate。

我们具体看一下,下面是FlutterActivity.onCreate()的代码。

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 eventDelegate.onCreate(savedInstanceState);
 }

FlutterActivity.$onCreate()比较简单,调用了super的onCreate()eventDelegate.onCreate() ,也就是调用了代理类的onCreate方法,下面分析FlutterActivityDelegate。

④FlutterActivityDelegate分析

从上面的分析可以得出结论,FlutterActivity什么都没有做,都交个了FlutterActivityDelegate去干,这里类实现了PluginRegistry,下面是FlutterActivityDelegate的代码。

public final class FlutterActivityDelegate
 implements FlutterActivityEvents,
   FlutterView.Provider,
   PluginRegistry {
		@Override
 public void onCreate(Bundle savedInstanceState) {
 
 }   
}

还是先分析FlutterActivityDelegate.onCreate() ,这个真正干活的onCreate方法还是比较复杂的,下面是FlutterActivityDelegate.onCreate()的代码。

@Override
public void onCreate(Bundle savedInstanceState) {
 String[] args = getArgsFromIntent(activity.getIntent());//1
 FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);//2
 flutterView = viewFactory.createFlutterView(activity);//3
 if (flutterView == null) {
 FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
 flutterView = new FlutterView(activity, null, nativeView);//4
 flutterView.setLayoutParams(matchParent);
 activity.setContentView(flutterView);//5
 launchView = createLaunchView();
 if (launchView != null) {
  addLaunchView();
 }
 }
 if (loadIntent(activity.getIntent())) {
 return;
 }
 String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
 if (appBundlePath != null) {
 runBundle(appBundlePath);
 }
}

注释1

得到了一些参数,这些参数是干啥用的?我们拿一个trace-startup参数为例进行简单介绍下面是FlutterActivityDelegate.getArgsFromIntent()的代码。

private static String[] getArgsFromIntent(Intent intent) {
 ArrayList<String> args = new ArrayList<>();
 if (intent.getBooleanExtra("trace-startup", false)) {
 args.add("--trace-startup");
 }
 if (intent.getBooleanExtra("start-paused", false)) {
 args.add("--start-paused");
 }
 
 if (!args.isEmpty()) {
 String[] argsArray = new String[args.size()];
 return args.toArray(argsArray);
 }
 return null;
}

当你安装一个App的时候,可以用下面这个命令,

flutter run --trace-startup --profile

安装完之后会生下面这个json,

{
 "engineEnterTimestampMicros": 273508186457,
 "timeToFrameworkInitMicros": 271420,
 "timeToFirstFrameMicros": 469796,
 "timeAfterFrameworkInitMicros": 198376
}

这个json会显示进入Flutter引擎的时间和展示应用第一帧的时间等等。

注释2

调用 FlutterMain.ensureInitializationComplete() ,这方法初始化了Flutter,下面是ensureInitializationComplete的代码。

 public static void ensureInitializationComplete(Context applicationContext, String[] args) {
 if (Looper.myLooper() != Looper.getMainLooper()) {
  throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
 }
 try {
  sResourceExtractor.waitForCompletion();
  List<String> shellArgs = new ArrayList<>();
  shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
 
  String appBundlePath = findAppBundlePath(applicationContext);
  String appStoragePath = PathUtils.getFilesDir(applicationContext);
  
  nativeInit(applicationContext, shellArgs.toArray(new String[0]),
  appBundlePath, appStoragePath, engineCachesPath);//1
 } catch (Exception e) {
  Log.e(TAG, "Flutter initialization failed.", e);
  throw new RuntimeException(e);
 }
 }
 
		//native方法
 private static native void nativeInit(Context context, String[] args, String 				bundlePath, String appStoragePath, String engineCachesPath);

先判断是不是主线程,如果不是主线程直接抛出异常。初始化参数调用 FlutterMain.nativeInit()方法,这个方法是native方法,主要的用途是初始化Flutter。

注释3

ViewFactory是一个接口,ViewFactory.createFlutterView()的具体实现有两个,分别是FlutterActivity.createFlutterView()FlutterFragmentActivity.createFlutterView()现在这个两个具体实现都返回null,也就是一定会走到注释4。

注释4

创建FlutterView,那么FlutterView是什么呢?看一下类的声明,下面是FlutterView的声明的代码,

public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry 
 
}

原来是一 SurfaceView,这个就很容易理解了。

注释5

关键来了,下面是调用setContentView的代码,

activity.setContentView(flutterView);

把FlutterView加载到Activity中,折腾了半天,就是做了这样一件事,说白了就是创建了一个FlutterView,并且把这个view显示到屏幕上。

13.介绍下 flutter 与 weex 的区别

①Flutter

优点:

  • 跨平台
  • 性能强大,流畅,混合开发中,最接近原生开发的框架;
  • Dart语言简单易学;
  • 极大降低了开发成本。原来需要招一个iOS、一个安卓,用了flutter后,只需要招一个flutter人员就够了;

缺点:

  • Widget的类型难以选择,糟糕的UI控件API;
  • Dart 语言的生态小,精通成本比较高。
  • 开发工具版本升级后,修改量大;
  • 原生集成第三方SDK后,兼容性适配是个令人头痛的问题;
  • 代码可读性较差(嵌套地狱),对代码质量和管理要求较高;

简短版:flutter是个性能强大的框架,但是坑多。

②Weex

特点:

  • 页面的开发目前支持Rax和Vue
  • 一次编写,三端(Android、iOS、前端)运行
  • UI 的绘制通过 native 的组件,JavaScript 逻辑在 JS 引擎里运行,两者通过 JavaScriptCore 通信。
  • 支持 Native 扩展
  • 可以在chrome中调试JS代码,weex支持在chrome中预览页面dom节点
  • 异步:weex只支持callback

如果要说优势的话,大概就是 vue 的开发者学习成本较低(在抛开 weex 自身的坑的情况下),这有个好处就是,一些简单的 vue 页面,可以方便的在 app 内运行出更好的性能,比如 uni-app 中支持 weex 的作用。

14.组件化介绍一下

12W字;2022最新Android11位大厂面试专题(二)

①组件化和模块化的区别

组件化就是把可以复用的、独立的、基础的、功能专一的代码封装到一个方法或者代码片段里,在未来需要的地方引入使用。用极少的代码实现之前相同的功能,避免了相同功能代码的复写,提高了开发的效率。在未来对改组件功能进行修改的时候只需要修改组件代码就可修改项目里所有的相同功能。组件化属于纵向分块,每个组件就像一个竖直的线永不相交。

模块化是为了单独实现某一功能模块进行封装的方法,一个模块里可能拥有n个基础组件搭配产生。模块化属于横向分块,每个模块像一条横向把n条竖直的线串联起来形成一个整体。

组件相当于库,模块相当于框架。

对比

12W字;2022最新Android11位大厂面试专题(二)

②组件之间的跳转和组件通信原理机制

如果一个单体项目进行组件化架构改造,应从以下方面入手

  • 代码解耦
  • 组件单独运行
  • 组件间通信
  • UI跳转
  • 组件生命周期
  • 调试
  • 代码隔离

众所周知,Android 提供了很多不同的信息的传递方式,比如在四大组件中本地 广播、进程间的 AIDL、匿名间的内存共享、Intent Bundle 传递等等,那么在这 么多传递方式,哪种类型是比较适合组件与组件直接的传递呢。

  • 本地广播,也就是 LoacalBroadcastRecevier。更多是用在同一个应用内的不同系 统规定的组件进行通信,好处在于:发送的广播只会在自己的 APP 内传播,不 会泄漏给其他的 APP,其他 APP 无法向自己的 APP 发送广播,不用被其他 APP 干扰。本地广播好比对讲通信,成本低,效率高,但有个缺点就是两者通信机制 全部委托与系统负责,我们无法干预传输途中的任何步骤,不可控制,一般在组 件化通信过程中采用比例不高。
  • 进程间的 AIDL。这个粒度在于进程,而我们组件化通信过程往往是在线程中, 况且 AIDL 通信也是属于系统级通信,底层以 Binder 机制,虽说 Android 提供模 板供我们实现,但往往使用者不好理解,交互比较复杂,往往也不适用应用于组 件化通信过程中。
  • 匿名的内存共享。比如用 Sharedpreferences,在处于多线程场景下,往往会线 程不安全,这种更多是存储一一些变化很少的信息,比如说组件里的配置信息等 等。
  • Intent Bundle 传递。包括显性和隐性传递,显性传递需要明确包名路径,组件 与组件往往是需要互相依赖,这背离组件化中 SOP(关注点分离原则),如果走 隐性的话,不仅包名路径不能重复,需要定义一套规则,只有一个包名路径出错, 排查起来也稍显麻烦,这个方式往往在组件间内部传递会比较合适,组件外与其 他组件打交道则使用场景不多。

说了这么多,那组件化通信什么机制比较适合呢?既然组件层中的模块是相互独 立的,它们之间并不存在任何依赖。没有依赖就无法产生关系,没有关系,就无 法传递消息,那要如何才能完成这种交流?

目前主流做法之一就是引入第三者。组件层的模块都依赖于基础层,从而产生第三者联系,这种第三者联系最终会编 译在 APP Module 中,那时将不会有这种隔阂,那么其中的 Base Module 就是 跨越组件化层级的关键,也是模块间信息交流的基础。比较有代表性的组件化开 源框架有得到 阿里 Arouter等等。

③事件总线

除了这种以通过引入第三者方式,还有一种解决方式是以事件总线方式,但这种 方式目前开源的框架中使用比例不高,如图:

12W字;2022最新Android11位大厂面试专题(二)

事件总线通过记录对象,使用监听者模式来通知对象各种事件,比如在现实生活 中,我们要去找房子,一般都去看小区的公告栏,因为那边会经常发布一些出租 信息,我们去查看的过程中就形成了订阅的关系,只不过这种是被动去订阅,因 为只有自己需要找房子了才去看,平时一般不会去看。小区中的公告栏可以想象 成一个事件总线发布点,监听者则是哪些想要找房子的人,当有房东在公告栏上 贴上出租房信息时,如果公告栏有订阅信息功能,比如引入门卫保安,已经把之 前来这个公告栏要查看的找房子人一一进行电话登记,那么一旦有新出租消息产 生,则门卫会把这条消息一一进行短信群发,那么找房子人则会收到这条消息进 行后续的操作,是马上过来看,还是延迟过来,则根据自己的实际情况进行处理。

在目前开源库中,有 EventBusRxBus 就是采用这种发布/订阅模式,优点是简 化了 Android 组件之间的通信方式,实现解耦,让业务代码更加简洁,可以动态 设置事件处理线程和优先级,缺点则是每个事件需要维护一个事件类,造成事件 类太多,无形中加大了维护成本。那么在组件化开源框架中有 ModuleBus 等 等。

事件总线,又可以叫做组件总线,以 ModuleBus 框架的源码为例,这个方案特别之处在于其借鉴了 EventBus 的思想,组件的注册/注销和组件调用的事件发送都跟 EventBus 类似,能够传递一些 基础类型的数据,而并不需要在 Base Moudel 中添加额外的类。所以不会影响 Base 模块的架构,但是无法动态移除信息接收端的代码,而自定义的事件信息 类型还是需要添加到 Base Module 中才能让其他功能模块索引。

其中的核心代码是在与 ModuleBus 类,其内部维护了两个 ArrayMap 键对值列 表,如下:

private static ArrayMap<Object,ArrayMap<String,MethodInfo>> 
moduleEventMethods = new ArrayMap<>(); 

private static ArrayMap<Class<?>,ArrayMap<String,ArrayList<Object>>> 
 moduleMethodClient = new ArrayMap<>()

在使用方法上,在 onCreate()onDestroy()中需要注册和解绑,比如

ModuleBus.getInstance().register(this); 

ModuleBus.getInstance().unregister(this);

最终使用类似 EventBuspost 方法一样,进行两个组件间的通信。这个框架 的封装的 post 方法如下

public void post(Class<?> clientClass,String methodName,Object...args){
    if(clientClass == null || methodName == null ||methodName.length() == 0) return;
    ArrayList<Object> clientList = getClient(clientClass,methodName) 
    
    for(Object c: clientList){ ArrayMap<String,MethodInfo> methods = moduleEventMethods.get(c); 
      Method method = methods.get(methodName).m; 
          method.invoke(c,args);
 }

可以看到,它是通过遍历之前内部的 ArrayMap,把注册在里面的方法找出,根据传入的参数进行匹配,使用反射调用。

④接口+路由

相对于事件总线的方式,组件间通信更多使用的还是基于 Base Module 的接口+路由的方式

接口+路由实现方式则相对容易理解点,我之前实践的一个项目就是通过这种方式实现的。实现思路是专门抽取一个 LibModule 作为路由服务,每个组件声明自己提供的服务 Service API,这些 Service 都是一些接口,组件负责将这些 Service 实现并注册到一个统一的路由 Router 中去,如果要使用某个组件的功能,只需要向 Router 请求这个 Service 的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。比如定义两个路由地址,一个登陆组件,一个设置组件,核心代码:

public class RouterPath {
    public static final String ROUTER_PATH_TO_LOGIN_SERVICE = "/login/service"; 
    public static final String ROUTER_PATH_TO_SETTING_SERVICE = "/setting/service"; }

那么就相应着就有两个接口 API,如下:

public interface ILoginProvider extends IProvider { 
    void goToLogin(Activity activity); 
 } 
      public interface ISettingProvider extends IProvider { 
void goToSetting(Activity activity); 
  } 
}

这两个接口 API 对应着是向外暴露这两个组件的能提供的通信能力,然后每个组 件对接口进行实现,如下:

@Override
  public void init(Context context) { 
} 
@Override 
  public void goToLogin(Activity activity) { 
    Intent loginIntent = new Intent(activity, LoginActivity.class); 
activity.startActivity(loginIntent);
   } 
}

这其中使用的到了阿里的 ARouter 页面跳转方式,内部本质也是接口+实现方式 进行组件间通信。调用则很简单了,如下:

ILoginProvider loginService = (ILoginProvider)       
   ARouter.getInstance().build(RouterPath.ROUTER_PATH_TO_LOGIN_SERVICE).naviga tion(); 
            if(loginService != null){
loginService.goToLogin(MainActivity.this); 
}

还有一个组件化框架,就是 ModularizationArchitecture ,它本质实现方式也是 接口+实现,但是封装形式稍微不一样点,它是每个功能模块中需要使用注解建立 Action 事件,每个 Action 完成一个事件动作。invoke 只是方法名为反射,并 未用到反射,而是使用接口方式调用,参数是通过 HashMap 传递的,无法传递 对象。具体详解可以看这篇文章 Android 架构思考(模块化、多进程)。

⑤页面跳转

页面跳转也算是一种组件间的通信,只不过它相对粒度更细化点,之前我们描述 的组件间通信粒度会更抽象点,页面跳转则是定位到某个组件的某个页面,可能 是某个 Activity,或者某个 Fragment,要跳转到另外一个组件的 ActivityFragment,是这两者之间的通信。甚至在一般没有进行组件化架构的工程项目 中,往往也会封装页面之间的跳转代码类,往往也会有路由中心的概念。不过一 般 UI 跳转基本都会单独处理,一般通过短链的方式来跳转到具体的 Activity

每个组件可以注册自己所能处理的短链的 SchemeHost,并定义传输数据的 格式,然后注册到统一的 UIRouter 中,UIRouter 通过 Scheme 和 Host 的匹 配关系负责分发路由。但目前比较主流的做法是通过在每个 Activity上添加注 解,然后通过 APT 形成具体的逻辑代码。下面简单介绍目前比较主流的两个框架核心实现思路:

ARouter ARouter 核心实现思路是,我们在代码里加入的 @Route 注解,会在编译时期通 过 apt 生成一些存储 pathactivityClass 映射关系的类文件,然后 app 进程启 动的时候会拿到这些类文件,把保存这些映射关系的数据读到内存里(保存在 map 里),然后在进行路由跳转的时候,通过 build()方法传入要到达页面的路由 地址,ARouter 会通过它自己存储的路由表找到路由地址对应的 Activity.class(activity.class = map.get(path)),然后 new Intent(),当调用 ARouterwithString()方法它的内部会调用 intent.putExtra(String name, String value), 调用 navigation()方法,它的内部会调用 startActivity(intent)进行跳转,这样便可 以实现两个相互没有依赖的 module顺利的启动对方的 Activity 了。

ActivityRouter 核心实现思路是,它是通过路由 + 静态方法来实现,在静态方 法上加注解来暴露服务,但不支持返回值,且参数固定位(context, bundle),基 于 apt 技术,通过注解方式来实现 URL 打开 Activity 功能,并支持在 WebView 和外部浏览器使用,支持多级 Activity 跳转,支持 BundleUri 参数注入并转换 参数类型。它实现相对简单点,也是比较早期比较流行的做法,不过学习它也是 很有参考意义的。

⑥组件化和插件化的区别

  • 组件化:是将一个App分成多个模块,每个模块都是一个组件(module),开发过程中可以让这些组件相互依赖或独立编译、调试部分组件,但是这些组件最终会合并成一个完整的Apk去发布到应用市场。
  • 插件化:是将整个App拆分成很多模块,每个模块都是一个Apk(组件化的每个模块是一个lib),最终打包的时候将宿主Apk和插件Apk分开打包,只需发布宿主Apk到应用市场,插件Apk通过动态按需下发到宿主Apk

15.webview 与 js 交互的实现方法

获取WebView对象

调用WebView对象的getSetTings()方法,获取WebSetTings对象

调用WebSetTings对象的setJavaScriptEnabled()方法,设置js可用,参数:布尔值

在判断是否支持js的时候,不要用alert(),默认不起作用,可以先用document.write()测试

调用WebView对象的addJavascripTinterface(obj,interfaceName)方法,添加js接口,参数:Object对象,String接口名称(这个对象)在js中的别名)

定义一个内部类MyJavascript

定义一个方法showToast(),显示吐司,api版本大于17需要加注解@JavascripTinterface

java代码:

package com.tsh.mywebview;
import android.Annotation.SuppressLint;
import android.app.Activity;
import android.app.progressDialog;
import android.graphics.bitmap;
import android.os.bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.webkit.JavascripTinterface;
import android.webkit.WebSetTings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
public class MainActivity extends Activity {
private WebView webview;
private ProgressDialog pd;
@Override
protected void onCreate(Bundle savedInstanceStatE) {
super.onCreate(savedInstanceStatE);
requestWindowFeature(Window.FEATURE_NO_titlE);
setContentView(R.layout.activity_main);
pd=new ProgressDialog(this);
pd.setmessage("正在加载...");
//webview的简单设置
webview=(WebView) findViewById(R.id.wv_internet);
//http://100.65.187.106/test.PHP
webview.loadUrl("http://100.65.187.106/test.PHP");
WebSetTings websetTings=webview.getSetTings();
websetTings.setSupportZoom(true);
websetTings.setBuilTinZoomControls(true);
//js交互
new MyJavascript().showToast("111");
websetTings.setJavaScriptEnabled(true);
webview.addJavascripTinterface(new MyJavascript(),"Android");
webview.loadUrl("javascript:documentWrite('测试')");
webview.setWebViewClient(new WebViewClient(){
@Override
public void onPageStarted(WebView view,String url,Bitmap favicon) {
pd.show();
}
@Override
public void onPageFinished(WebView view,String url) {
pd.dismiss();
}
});
}
//暴露给js的功能接口
public class MyJavascript{
//显示吐司
// 如果target 大于等于API 17,则需要加上如下注解
@JavascripTinterface
public void showToast(String text) {
Toast.makeText(MainActivity.this,text,1).show();
}
//显示loading
@JavascripTinterface
public void showProgressDialog(String text) {
pd.setmessage(text);
pd.show();
}
}
//后退键
@Override
public Boolean onKeyDown(int keyCode,KeyEvent event) {
if(keyCode==KeyEvent.KEYCODE_BACK&&webview.canGoBACk()){
webview.goBACk();
return true;
}
return super.onKeyDown(keyCode,event);
}
//菜单键
@Override
public Boolean onCreateOptionsMenu(Menu menu) {
menu.add(0,"刷新");
menu.add(0,1,"后退");
menu.add(0,2,"前进");
return super.onCreateOptionsMenu(menu);
}
//菜单点击事件
@Override
public Boolean onOptionsItemSELEcted(MenuItem item) {
switch (item.getOrder()) {
case 0:
webview.reload();
break;
case 1:
if(webview.canGoBACk()){
webview.goBACk();
}
break;
case 2:
if(webview.canGoForWARD()){
webview.goForWARD();
}
break;
}
return super.onOptionsItemSELEcted(item);
}
}

JS代码

<html>
<head>
<Meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>测试android程序</title>
</head>
<body>
测试android和js交互
<br/>
<button onClick="showToast()">显示吐司</button>
<br/>
<button onClick="showProgressDialog()">显示loading</button>
<script type="text/javascript">
function showToast(){
Android.showToast("显示吐司");
}
function showProgressDialog(){
Android.showProgressDialog("显示进度条");
}
</script>
</body>
</html>

16.介绍下 flutter_boost 的原理

了解过Flutter如何与Native(Android)进行交互,有了这个知识就很容易理解flutter-boost原理。那么它是怎么实现的?

flutter-boost自定义了一个Activity —— BoostFlutterActivity,使用的时候会通过NewEngineIntentBuilder创建一个Intent,它的build代码:

public Intent build(@NonNull Context context) {
    ...
    return new Intent(context, activityClass)
            .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
            .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false)
            .putExtra(EXTRA_URL, url)
            .putExtra(EXTRA_PARAMS, serializableMap);
}

可以看到,它不仅仅支持route,同时还支持传参params,这正是我们需要的,那么这是怎么实现的?

首先看它的onCreate函数:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);
    ...
    setContentView(createFlutterView());
    ...
}

通过createFlutterView创建了一个view,并setContentView。在createFlutterView中:

protected View createFlutterView() {
    return delegate.onCreateView(null,null,null);
}

delegate是FlutterActivityAndFragmentDelegate对象,它的onCreateView:

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this);
    ensureAlive();
    flutterView = new XFlutterView(host.getActivity(), FlutterBoost.instance().platform().renderMode(), host.getTransparencyMode());
    ...
    mSyncer.onCreate();
    return flutterSplashView;
}

第一行通过containerManager().generateSyncer创建了一个mSyncer,containerManager()得到的是一个FlutterViewContainerManager对象,它的generateSyncer:

@Override
public IOperateSyncer generateSyncer(IFlutterViewContainer container) {
    ...
    ContainerRecord record = new ContainerRecord(this, container);
    ...
    mRefs.add(new ContainerRef(record.uniqueId(),container));
    return record;
}

这里可以看到mSyncer实际上是一个ContainerRecord。这个很重要,后面会通过它实现route。

但是目前还没看到url和params是如何被使用的,那么回头看BoostFlutterActivity的onResume函数,BoostFlutterActivity所有生命周期函数都会调用delegate对应的函数,所以直接看它的onResume:

public void onResume() {
    mSyncer.onAppear();
    ...
}

可以看到一开始就调用了mSyncer的onAppear函数,而mSyncer前面已经知道是ContainerRecord,那么它的onAppear:

@Override
public void onAppear() {
    ...
    mState = STATE_APPEAR;
    mManager.pushRecord(this);
    mProxy.appear();
    mContainer.getBoostFlutterView().onAttach();
}

重点是mProxy.appear(),这个mProxy是内部类MethodChannelProxy,它的appear:

private void appear() {
    invokeChannelUnsafe("didShowPageContainer",
            mContainer.getContainerUrl(),
            mContainer.getContainerUrlParams(),
            mUniqueId
    );
    mState = STATE_APPEAR;
}

这里我们看到了mContainer.getContainerUrl()mContainer.getContainerUrlParams(),这个mContainer就是最开始FlutterBoost.instance().containerManager().generateSyncer(this);传入的this,就是FlutterActivityAndFragmentDelegate,它的这两个函数(Host接口的)则调用了BoostFlutterActivity(实现了Host)对应的函数:

@Override
public String getContainerUrl() {
    if (getIntent().hasExtra(EXTRA_URL)) {
        return getIntent().getStringExtra(EXTRA_URL);
    }
    return "";
}

@Override
public Map getContainerUrlParams() {
    if (getIntent().hasExtra(EXTRA_PARAMS)) {
        SerializableMap serializableMap = (SerializableMap) getIntent().getSerializableExtra(EXTRA_PARAMS);
        return serializableMap.getMap();
    }
    Map<String, String> params = new HashMap<>();
    return params;
}

这就是我们传入的url和params。所以接下来就来看invokeChannelUnsafe函数是怎么执行的:

public void invokeChannelUnsafe(String method, String url, Map params, String uniqueId) {
    HashMap<String, Object> args = new HashMap<>();
    args.put("pageName", url);
    args.put("params", params);
    args.put("uniqueId", uniqueId);
    FlutterBoost.instance().channel().invokeMethodUnsafe(method, args);
}

FlutterBoost的channel()返回的是FlutterBoostPlugin,它的invokeMethodUnsafe层层调用最终执行:

public void invokeMethod(final String name, Serializable args, MethodChannel.Result result) {
    ...
    mMethodChannel.invokeMethod(name, args, result);
}

mMethodChannel是MethodChannel类型,这个我们之前重点讲解了,是native和flutter的交互方式之一。所以最终就是执行了flutter的didShowPageContainer,并将url和params作为参数传入。那么flutter中如何处理的?经过搜索发现是在ContainerCoordinator类中:


Future<dynamic> _onMethodCall(MethodCall call) {
    final String pageName = call.arguments['pageName'] as String;
    final Map<String, dynamic> params =
        (call.arguments['params'] as Map<dynamic, dynamic>)
            ?.cast<String, dynamic>();
    final String uniqueId = call.arguments['uniqueId'] as String;

    switch (call.method) {
      ...
      case 'didShowPageContainer':
        nativeContainerDidShow(pageName, params, uniqueId);
        break;
      ...
    }
    return Future<dynamic>(() {});
  }
  
  bool nativeContainerDidShow(
  String name,
  Map<String, dynamic> params,
  String pageId,
) {
  FlutterBoost.containerManager
      ?.showContainer(_createContainerSettings(name, params, pageId));

  // Compatible to accessibility mode on Android.
  if (Platform.isAndroid) {
    try {
      final SemanticsOwner owner =
          WidgetsBinding.instance.pipelineOwner?.semanticsOwner;
      final SemanticsNode root = owner?.rootSemanticsNode;
      root?.detach();
      root?.attach(owner);
    } catch (e) {
      assert(false, e.toString());
    }
  }

didShowPageContainer对应的执行方法是nativeContainerDidShow,它的第一行代码执行了containerManager.showContainer,containerManager是BoostContainerManager,它的showContainer:

void showContainer(BoostContainerSettings settings) {
    if (settings.uniqueId == _onstage.settings.uniqueId) {
      _onShownContainerChanged(null, settings.uniqueId);
      return;
    }

    final int index = _offstage.indexWhere((BoostContainer container) =>
        container.settings.uniqueId == settings.uniqueId);
    if (index > -1) {
      _offstage.add(_onstage);
      _onstage = _offstage.removeAt(index);

      setState(() {});

      for (final BoostContainerObserver observer in FlutterBoost
          .singleton.observersHolder
          .observersOf<BoostContainerObserver>()) {
        observer(ContainerOperation.Onstage, _onstage.settings);
      }
      Logger.log('ContainerObserver#2 didOnstage');
    } else {
      pushContainer(settings);
    }
  }

如果该页面之前不存在,则执行pushContainer(settings):

void pushContainer(BoostContainerSettings settings) {
    assert(settings.uniqueId != _onstage.settings.uniqueId);
    assert(_offstage.every((BoostContainer container) =>
        container.settings.uniqueId != settings.uniqueId));

    _offstage.add(_onstage);
    _onstage = BoostContainer.obtain(widget.initNavigator, settings);

    setState(() {});

    for (final BoostContainerObserver observer in FlutterBoost
        .singleton.observersHolder
        .observersOf<BoostContainerObserver>()) {
      observer(ContainerOperation.Push, _onstage.settings);
    }
    Logger.log('ContainerObserver#2 didPush');
  }

这里通过BoostContainer.obtain来创建一个widget并赋值给_onstage,这个函数源码:

factory BoostContainer.obtain(
      Navigator navigator,
      BoostContainerSettings settings,
      ) =>
      BoostContainer(
        key: GlobalKey<BoostContainerState>(),
        settings: settings,
        onGenerateRoute: (RouteSettings routeSettings) {
          if (routeSettings.name == '/') {
            return BoostPageRoute<dynamic>(
              pageName: settings.name,
              params: settings.params,
              uniqueId: settings.uniqueId,
              animated: false,
              settings: RouteSettings(
                name: settings.name,
                arguments: routeSettings.arguments,
              ),
              builder: settings.builder,
            );
          } else {
            return navigator.onGenerateRoute(routeSettings);
          }
        },
        observers: <NavigatorObserver>[          ContainerNavigatorObserver.bindContainerManager(),          HeroController(),        ],
        onUnknownRoute: navigator.onUnknownRoute,
      );

可以看到这就是通过我们熟悉的RouteFactory来创建widget,这样就实现了router,同时也实现的传参。

观察BoostContainerManager(container_mannager.dart)可以发现,_onstage是当前展示的页面,而_offstage则是前级页面,而布局时其实全部堆叠的:

final List<BoostContainer> containers = <BoostContainer>[];
containers.addAll(_offstage);

assert(_onstage != null, 'Should have a least one BoostContainer');
containers.add(_onstage);

这样就通过改变_onstage和_offstage来实现页面的切换,所以flutter-boost本质上是用一个页面切换不同的内容,而所有页面都公用一个flutter engine(都是进一个页面,所以initialRoute固定),这样除了第一次打开,后面再次打开就会很快,实现的启动加速。

这样我们就大致的了解了flutter-boost的启动原理,当然flutter-boost还有很多功能,不过了解了这个启动原理,我们可以试着自己来实现一个简单的框架。

一共50W字的文档,面试专题12W字只是一小部分,字数限制,分几篇更。

关注公众号:Android苦做舟

提前解锁 《整套50W字Android体系PDF》,让学习更贴近未来实战。

总共囊括

1.腾讯Android开发笔记(33W字)

2.2022最新Android十一位大厂面试专题(12W字)

3.音视频经典面试题(6W字)

4.Jetpack全家桶

5.Android 性能监控框架Matrix

6.JVM

7.车载应用开发