由于近期许多小伙伴开端面试了,所以在许多的刷题,也有许多问到我有没有一些大厂面试题或许常见的面试题,字节参阅一下,于是乎花了一周时刻收拾出这份 《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 源码 ;
③剖析 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的当地就会堵塞;
④主线程中Looper的死循环为什么没有导致体系卡死?
- 咱们的UI线程主线程其实是ActivityThread线程,而一个线程只会有一个Looper;
- ActivityThread.java的main函数是一个APP进程的进口,假如不卡死,main函数履行完则整个运用进程就会退出;
- android是以事情为驱动的操作体系,当有事情来时,就去做对应的处理,没有时就显现静态界面;
获取当时线程:Thread.currentThread();
ThreadLocalMap
:相似于HashMap;
每个Thread方针都有一个对应的ThreadLocalMap;
在Looper.prepare()
时,存入Looper,存Looper时ThreadLocalMap
的key为ThreadLocal
,value
为Looper
;
内存颤动底子的处理办法是复用;
handler.obtainMessage();
-
从Looper的收回池中取Message;
-
Message是一个单向链表,Message不是一个单纯的方针,而是一个链表调集
-
最大长度固定50个
Linux函数: epoll_create:App注册进红黑树中,拿到一个事情fd的值; epoll_ctl:注册事情类型,监听fd是否改动(Linux中事情都会被写入文件中,如接触屏幕事情会写入到:dev/input/event0文件中),fd有改动时唤醒epoll_wait; epoll_wait:有事情时就分发,没事情就堵塞
⑤总结:
handler怎样做的线程切换的? 首先Handler的运用进程:
-
调用
Looper.prepare();
-
创立Handler方针;
-
调用
Looper.Loop()
办法。 -
线程中发送音讯。
在榜首步时,创立一个Looper,并放到当时线程的变量threadLocals
中;threadLocals
是一个map,key为ThreadLocal方针本身,value为Looper;在Looper.loop()
时取出;
第二步,用户在当时线程(或许是子线程)创立Handler方针;
第三步,Looper.loop()
一向在死循环,Looper.loop()
这句代码下面的代码是不会被调用的,调用Looper.loop()
函数时,先从当时线程的map变量中取出Looper,再从Looper中拿到行列MessageQueue,for循环中不断从行列中取出音讯
第四步,在其他线程调用handelr发送音讯时,Message里有个target,便是发送音讯的handler;
在Looper.loop()
时,行列中取到音讯时,调用msg.target.dispatchMessage(msg);
其实便是handler方针.dispatchMessage(msg);
所以不论在哪个线程调用发送音讯,都会调用到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对话框;
详细:
- App进程的进口为
ActivityThread.java的main()
函数,留意ActivityThread
不是一个线程; - 运用的ui主线程实践是调用
ActivityThread.java的main()
函数履行时地点的线程,而这个线程对咱们不可见,可是这便是主线程;参阅: - 在
ActivityThread.java
的main()
函数中,会调用Looper.prepareMainLooper();
-
Looper.prepareMainLooper()
会创立一个Looper并放到当时线程(主线程)的变量threadLocals
中进行绑定,threadLocals
是一个ThreadLocal.ThreadLocalMap;
- 在
ActivityThread.java
的main()
函数结尾,敞开Looper.loop()
进行死循环,不让main函数完毕,然后让App进程不会完毕; - Android体系是以事情作为驱动的操作体系,当有事情来时,就去做对应处理,没有事情时,就显现当时界面,不做其他剩余操作(浪费资源);
- 在
Looper.loop()
的死循环中,不只要取用户发的事情,还要取体系内核发的事情(如屏幕亮度改动等等); - 在调用
Looper.loop()
时,从MessageQueue.next()
中获取事情,若没有则堵塞,有则分发; -
MessageQueue
其实不是一个行列,用epoll机制完结了堵塞; - 在
Looper.prepareMainLooper()
时,调用c++函数epoll_create()
会将App注册进epoll机制的红黑树中得到fd的值,epoll_ctl()给每个App注册事情类型并监听fd值是否改动,fd有改动时唤醒epoll_wait; -
epoll_wait()
有事情时就分发,没事情就堵塞
⑦子线程的Looper和子线程Looper有什么不同?
子线程Looper是能够退出的,主线程不行;
4.求二叉树中两个节点之间的最大间隔。
例如这样一棵二叉树:
什么叫做二叉树结点间的最大间隔呢?例如从结点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);
}
我需求取得某一个结点的左子树和右子树的状况,当然对应于第三种状况,两头树高再加上结点本身便是间隔,这三个数比较一下巨细,很简略就能算出最大间隔,一起,还要记载算上当时结点的树高,全部计算完毕后,把这个方针回来出去即可。
运转也无问题:
5.谈谈你对Binder的认识?
①为什么要用Binder?
- Android体系内核是Linux内核
- Linux内核进程通讯有:管道、内存同享、Socket、File;
- 对比:
Binder的一次复制发生在用户空间复制到内核空间;
用户空间: App进程运转的内存空间;
内核空间: 体系驱动、和硬件相关的代码运转的内存空间,也便是进程ID为0的进程运转的空间;
程序局部性准则: 只加载少数代码;运用没有运转的代码放在磁盘中,运转时高速缓冲区进行加载要运转的代码;默许一次加载一个页(4K),若不行4K就用0补齐;
MMU:内存办理单元;
给CPU供给虚拟地址;
当对变量操作赋值时:
- CPU拿着虚拟地址和值给到MMU
- MMU用虚拟地址匹配到物理地址,MMU去物理内存中进行赋值;
物理地址: 物理内存的实践地址,并不是磁盘;
虚拟地址: MMU依据物理内存的实践地址翻译出的虚拟地址;供给给CPU运用;
页射中: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发送的数据
- 经过内存映射联系,只发生了一次复制
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:数据接纳角色
编译器生成的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发动记载中
②ActivityThread
-
每个运用有一个ActivityThread;是运用的进口;
-
在APP进程中
-
是AMS的缓存中心
-
ActivityThread中的List activtes放了activity的发动记载
③ActivityThread中重要的方针:
- ApplicationThread:AMS回调给ActivityThread数据的桥梁
- mInstrumentation:办理Application和Activity的生命周期(及创立)
- mH:Handler,处理ApplicationThread里各种回调函数发送的各种音讯
④点击桌面App图标发生了什么?
- 点击的APP图标是在独自的Luancher进程,是一个体系App进程
- Luancher进程恳求SystemServer进程中的AMS去创立运用的根Activity(AndroidMnifest.xml中initen-fifter为Luanche的activity)
- AMS经过包名让PMS查询到相关运用信息,得到运用的Package;
- AMS创立activity栈,依据Package拿到根activity的装备节点信息,放到栈中,此刻栈中只有一个根activity的装备节点信息,也便是在栈顶;(此处的栈不是运用层的栈,这个栈只是用来放activity节点信息的)
- AMS恳求zygote进程创立App进程;zygote进程比较特殊, 运用Socket通讯,而不是binder;zygote是一切运用的孵化器,zygote进程挂掉时,手时机主动重启;
- zygote进程去fork出App进程;
- APP进程中的主线程调用
ActivityThread.main()
静态函数,main中创立ActivityThread
方针 - 接着在
ActivityThread.attch()
中创立了一个ApplicationThread
方针,作为和AMS通讯时,回来成果的桥梁; - App进程经过AMS的binder调用
attachApplication(thread)
恳求AMS获取运用对应的Applaction和栈顶中activity节点信息(进程4),此刻给AMS传过去了一个thread,这个thread便是ApplicationThread
- AMS将从PMS查到的application节点数据序列化后,调用
thread.bindApplaction
(data数据…)传给ActivityThread;
(此刻代码还会持续往下履行,去获取栈顶activity的节点信息) - ActivityThread调用sendMessage发送音讯
BIND_APPLICATION(110)
给Handler,Handler调用handleBindApplication(data)
- 经过反射实例化Instrumentation方针:担任application和activity的生命周期的办理
- 经过Instrumentation方针反射实例化
new Applaction
方针app - 调用
Instrumentation.callApplactionOnCreate(app)
- 履行
Applaction.onCreate()
- 进程10中AMS持续向下履行查找activity,AMS将查到的栈顶根Activity(LaunchActivity )信息封装到一个事务ClientTransaction中,提交事务并履行,在履行中,调用
thread.scheduleTransaction
(事务数据);(thread为ActivityThread中的ApplicationThread
) - 在
ApplicationThread
回调scheduleTransaction
函数中,发送`EXECUTE_TRANSACTION(159)音讯 - Handler处理
EXECUTE_TRANSACTION
音讯,从事务数据中取出LaunchActivity
信息,并调用hanldeLaunchActivity
(activity数据) - 经过Instrumentation方针反射实例化
newActivity()
出方针activity - 履行
activity.attach()
,在attach中创立WMS的桥接署理类;(制作流程会用到) - 经过
Instrumentation
调用callActivityOnCreate(activity)
- 履行
Activty.onCreate();
- 至此发动页根Activity发动完结;
下图中4-5中少了上面7-23的进程:
7-15创立并发动了Application;
16-22创立并发动了Activity;
⑤运用内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.组件化介绍一下
①组件化和模块化的差异
组件化便是把能够复用的、独立的、根底的、功用专一的代码封装到一个办法或许代码片段里,在未来需求的当地引进运用。用极少的代码完结之前相同的功用,防止了相同功用代码的复写,提高了开发的功率。在未来对改组件功用进行修正的时分只需求修正组件代码就可修正项目里一切的相同功用。组件化归于纵向分块,每个组件就像一个竖直的线永不相交。
模块化是为了独自完结某一功用模块进行封装的办法,一个模块里或许具有n个根底组件调配发生。模块化归于横向分块,每个模块像一条横向把n条竖直的线串联起来构成一个整体。
组件相当于库,模块相当于结构。
对比
②组件之间的跳转和组件通讯原理机制
假如一个单体项目进行组件化架构改造,应从以下方面入手
- 代码解耦
- 组件独自运转
- 组件间通讯
- UI跳转
- 组件生命周期
- 调试
- 代码阻隔
众所周知,Android
供给了许多不同的信息的传递办法,比方在四大组件中本地 播送、进程间的 AIDL
、匿名间的内存同享、Intent Bundle
传递等等,那么在这 么多传递办法,哪种类型是比较合适组件与组件直接的传递呢。
- 本地播送,也便是
LoacalBroadcastRecevier
。更多是用在同一个运用内的不同系 统规定的组件进行通讯,长处在于:发送的播送只会在自己的 APP 内传达,不 会走漏给其他的APP
,其他APP
无法向自己的APP
发送播送,不用被其他APP
搅扰。本地播送好比对讲通讯,本钱低,功率高,但有个缺陷便是两者通讯机制 全部托付与体系担任,咱们无法干涉传输途中的任何进程,不可操控,一般在组 件化通讯进程中采用份额不高。 - 进程间的
AIDL
。这个粒度在于进程,而咱们组件化通讯进程往往是在线程中, 况且AIDL
通讯也是归于体系级通讯,底层以Binder
机制,虽说Android
供给模 板供咱们完结,但往往运用者欠好了解,交互比较复杂,往往也不适用运用于组 件化通讯进程中。 - 匿名的内存同享。比方用
Sharedpreferences
,在处于多线程场景下,往往会线 程不安全,这种更多是存储逐个些改动很少的信息,比方说组件里的装备信息等 等。 -
Intent Bundle
传递。包括显性和隐性传递,显性传递需求明确包名途径,组件 与组件往往是需求彼此依靠,这背离组件化中 SOP(重视点别离准则),假如走 隐性的话,不只包名途径不能重复,需求界说一套规矩,只有一个包名途径犯错, 排查起来也稍显麻烦,这个办法往往在组件间内部传递会比较适宜,组件外与其 他组件打交道则运用场景不多。
说了这么多,那组件化通讯什么机制比较合适呢?已然组件层中的模块是彼此独 立的,它们之间并不存在任何依靠。没有依靠就无法发生联系,没有联系,就无 法传递音讯,那要怎样才干完结这种交流?
现在干流做法之一便是引进第三者。组件层的模块都依靠于根底层,然后发生第三者联系,这种第三者联系终究会编 译在 APP Module
中,那时将不会有这种隔膜,那么其间的 Base Module
便是 跨越组件化层级的要害,也是模块间信息交流的根底。比较有代表性的组件化开 源结构有得到 阿里 Arouter
等等。
③事情总线
除了这种以经过引进第三者办法,还有一种处理办法是以事情总线办法,但这种 办法现在开源的结构中运用份额不高,如图:
事情总线经过记载方针,运用监听者形式来通知方针各种事情,比方在现实生活 中,咱们要去找房子,一般都去看小区的公告栏,由于那边会经常发布一些租借 信息,咱们去检查的进程中就构成了订阅的联系,只不过这种是被动去订阅,因 为只有自己需求找房子了才去看,平时一般不会去看。小区中的公告栏能够幻想 成一个事情总线发布点,监听者则是哪些想要找房子的人,当有房东在公告栏上 贴上租借房信息时,假如公告栏有订阅信息功用,比方引进门卫保安,现已把之 前来这个公告栏要检查的找房子人逐个进行电话挂号,那么一旦有新租借音讯产 生,则门卫会把这条音讯逐个进行短信群发,那么找房子人则会收到这条音讯进 行后续的操作,是马上过来看,仍是推迟过来,则依据自己的实践状况进行处理。
在现在开源库中,有 EventBus
、RxBus
便是采用这种发布/订阅形式,长处是简 化了 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);
终究运用相似 EventBus
中 post
办法相同,进行两个组件间的通讯。这个结构 的封装的 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
,要跳转到别的一个组件的 Activity
或 Fragment
,是这两者之间的通讯。甚至在一般没有进行组件化架构的工程项目 中,往往也会封装页面之间的跳转代码类,往往也会有路由中心的概念。不过一 般 UI 跳转基本都会独自处理,一般经过短链的办法来跳转到详细的 Activity
。
每个组件能够注册自己所能处理的短链的 Scheme
和 Host
,并界说传输数据的 格局,然后注册到一致的 UIRouter
中,UIRouter
经过 Scheme
和 Host 的匹 配联系担任分发路由。但现在比较干流的做法是经过在每个 Activity
上增加注 解,然后经过 APT 构成详细的逻辑代码。下面简略介绍现在比较干流的两个结构中心完结思路:
ARouter
ARouter
中心完结思路是,咱们在代码里加入的 @Route
注解,会在编译时期通 过 apt
生成一些存储 path
和 activityClass
映射联系的类文件,然后 app
进程启 动的时分会拿到这些类文件,把保存这些映射联系的数据读到内存里(保存在 map
里),然后在进行路由跳转的时分,经过 build()
办法传入要抵达页面的路由 地址,ARouter
会经过它自己存储的路由表找到路由地址对应的 Activity.class(activity.class = map.get(path))
,然后 new Intent()
,当调用 ARouter
的 withString()
办法它的内部会调用 intent.putExtra(String name, String value)
, 调用 navigation()
办法,它的内部会调用 startActivity(intent)
进行跳转,这样便可 以完结两个彼此没有依靠的 module
顺畅的发动对方的 Activity
了。
ActivityRouter
中心完结思路是,它是经过路由 + 静态办法来完结,在静态方 法上加注解来露出服务,但不支撑回来值,且参数固定位(context
, bundle
),基 于 apt 技术,经过注解办法来完结 URL
翻开 Activity
功用,并支撑在 WebView 和外部浏览器运用,支撑多级 Activity
跳转,支撑 Bundle
、Uri
参数注入并转换 参数类型。它完结相对简略点,也是比较前期比较盛行的做法,不过学习它也是 很有参阅意义的。
⑥组件化和插件化的差异
-
组件化:是将一个
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.车载运用开发