面试总结

面试知识点记录整理


Java


Android

context

1.可以理解为一个环境中切换的不同场景,可以获取一些公共资源,例如资源文件如:Resources、AssetManager、Package应用包信息等;
可以启动 Activities/Service, 注册广播监听器,发送广播等。

2.四大组件中只有 Activity,Service 继承了 Context,所以如果要算应用里的 Context 的实例数量,应该是 Activity 的实例个数 + Service 的实例个数 + Application 的实例个数。

3.BroadcastReceiver是没有Context的,onReceiver传进来的Context是注册该广播的Context ,而ContentProvider的Context是Application的Context。

4.ContextWrapper中的mBase变量是如何实例化的(分activity和application)

Activity:
ActivityThread#performLaunchActivity
ContextImpl appContext = createBaseContextForActivity(r);

Application:
ActivityThread#performLaunchActivity
Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);

Handler

1.一个线程可以有几个Looper?
只能有一个,不然调用Looper.prepare()会抛出运行时异常,提示“Only one Looper may be created per thread”

2.一个线程可以有几个Handler
可以创建无数个Handler,但是他们使用的消息队列都是同一个,也就是同一个Looper

3.同一个Looper是怎么区分不同的Handler的,换句话说,不同的Handler是怎么做到处理自己发出的消息的?
handler的enqueueMessage中,msg.target = this,最后msg.target.dispatchMessage(msg)

4.Thread 若与 Looper 关联,将会是一一对应的关系,且关联后关系无法改变。
Looper 与 MessageQueue 是一一对应的关系。
Handler 与 Looper 是多对一的关系,创建 Handler 实例时要么提供一个 Looper 实例,要么使用当前线程有关联的Looper。

5.Handler的实现原理
Handler:负责消息的发送和处理
Message:消息对象,类似于链表的一个结点
MessageQueue:消息队列,用于存放消息对象的数据结构
Looper:消息队列的处理者
Handler发送消息时调用MessageQueue的enqueueMessage插入一条信息到MessageQueue,Looper不断轮询调用MeaasgaQueue的next方法 如果发现message就调用handler的dispatchMessage,dispatchMessage被成功调用,接着调用handlerMessage()。

6.子线程中不能直接new一个Handler,因为Handler 的构造方法中,会通过Looper.myLooper()获取looper对象,如果为空,则抛出异常,主线程则因为在ActivityThread的main方法中有Looper.prepareMainLooper()

7.Handler导致的内存泄露原因及其解决方案

【消息队列中的Message持有mHandler实例的引用(target),mHandler又持有Activity的引用】

Java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用
解决方案:
1.静态内部类 + 弱引用
private static class MyHandler extends Handler {
//弱引用,在垃圾回收时,activity可被回收
private WeakReference mWeakReference;
public MyHandler(MainActivity activity) {
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//之所以引用activity对象,是因为更新UI需要activity对象
}
}
2.在Activity销毁时,清空Handler中未执行或正在执行的Callback以及Message:
protected void onDestroy() {
super.onDestroy();
//清空handler管道和队列
mHandler.removeCallbacksAndMessages(null);
}

8.Message对象创建的方式
Message msg = new Message()
Message msg = Message.obtain()//避免重复Message创建对象

9.Handler的post与sendMessage的区别
sendMessage(适用多条件判断)
sendMessage-sendMessageAtTime-enqueueMessage
post(适用单一场景)
post-sendMessageDelayed-sendMessageAtTime-enqueueMessage
其中sendMessageDelayed中有sendMessageDelayed(getPostMessage(r), 0)
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

10.MessageQueue是什么数据结构
消息是按照时间先后顺序来存储的单链表

11.IdleHandler
Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// UI第一帧绘制完成(可以理解为页面可见)
return false;
}
});

12.消息屏障
message.target ==null为屏障消息,遇到target为null的Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理
1、Handler构造方法中传入async参数,设置为true,使用此Handler添加的Message都是异步的
2、创建Message对象时,直接调用setAsynchronous(true)

Glide

1.Glide的缓存机制,主要分为2种缓存,一种是内存缓存,一种是磁盘缓存。
内存缓存的原因是:防止应用重复将图片读入到内存,造成内存资源浪费。
磁盘缓存的原因是:防止应用重复的从网络或者其他地方下载和读取数据。

2.三级缓存:弱引用缓存-》Lru算法缓存-》磁盘缓存(弱引用作用:lru size比较小,界面图片又很多,导致还在显示的图片被lru删除)

3.弱引用缓存使用hashmap,LruCache使用LinkedHashMap,LinkedHashMap 继承HashMap,在 HashMap的基础上,新增了双向链表结构,每次访问数据的时候,会更新被访问的数据的链表指针,具体就是先在链表中删除该节点,然后添加到链表头header之前,这样就保证了链表头header节点之前的数据都是最近访问的(从链表中删除并不是真的删除数据,只是移动链表指针,数据本身在map中的位置是不变的)

4.图片尺寸不一样,即使是同一张图,Glide也会下载两次,因为缓存Key的生成条件之一就是控件的长宽(4.9)(新版本好像只下载一次,对原图进行变换)

5.Glide会创建透明的fragment来感知生命周期,当 Activity、Fragment 等组件进入不可见,或者已经销毁的时候,Glide 会停止加载资源(Glide.with(this)绑定了Activity的生命周期。在Activity内新建了一个无UI的Fragment)

6.DiskCacheStrategy.DATA// 表示只缓存原始图片,DiskCacheStrategy.RESOURCE// 表示只缓存转换过后的图片(缩放等操作)

7.图片压缩:inSampleSize进行尺寸优化,inpreferredconfig进行RGB优化,inBitmap进行bitmap复用

8.BitmapRegionDecoder可用于加载大图,滑动时内存抖动,卡顿现象比较明显

RecyclerView

setAdapter中会注册观察者,notifyxxx中被观察者会通知注册的观察者,执行相应的方法

viewholder主要是为了hold view来复用,避免大量findViewById操作(findViewById涉及到viewtree的递归遍历,很耗时,所以尽量将view扁平化,少一些嵌套)

ViewHolder中使用SparseArray替代HashMap存储viewId,数据量小的时候SparseArray性能略好,没有装箱

SnapHelper帮助RV实现类似viewpager的效果,横向效果

建议在onCreateViewHolder中使用listener.onItemClick(view, holder.getLayoutPosition())来实现点击事件

如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源,这样RecyclerView在onMeasure阶段可以直接计算出高度,不需要多次计算子ItemView的高度

可以通过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭提升效率。

tryGetViewHolderForPositionByDeadline 获取缓存的方法

dispatchLayoutStep1()预布局
dispatchLayoutStep2()真正布局
dispatchLayoutStep3()保存和触发有关动画的信息,相关清理等工作

RecyclerView滚动时先填充后缓存

1.notifyItemRemoved之后,pre-layout预布局,提前加载未显示底部item,便于整体的上移动画

2.CachedView的默认大小为2,可以通过 setItemViewCacheSize 方法修改它的值,RecycledViewPool默认大小为5,可以通过
RecyclerView.getRecycledViewPool().setMaxRecycledViews(int viewType, int max)来修改

3.RecycledViewPool取出后需要bindViewHolder,CachedView不需要

4.数据尽量异步处理,尽量将最优质的数据格式返回给UI线程

5.针对快速滑动事件,可以使用addOnScrollListener添加对快速滑动的监听,当用户快速滑动时,停止加载数据操作

7.manager.setSpanSizeLookup可以设置item列数

8.上拉加载更多数据,可以直接用notifyItemRangeInserted方法,不要用notifyDataSetChanged方法,或者直接交给diff

9.在Layout的过程会通过LayoutManager.fill去将RecyclerView填满
fill会调用LayoutManager.layoutChunk去生成一个具体的ViewHolder
然后LayoutManager就会调用Recycler.getViewForPosition向Recycler去要ViewHolder,然后调用tryGetViewHolderForPositionByDeadline,里面涉及到各个缓存,如果还是没找到,则直接创建

10.mAttachedScrap / mChangedScrap

11.notidyDataSetChanged() 会将屏幕中和离屏缓存 mCachedViews 中的 ViewHolder 全部标记为无效(RecyclerPool 中的 ViewHolder 本身已标记为无效)。在真正执行 RecyclerView#requestLayout() 刷新列表时,所有的 Item 都需要重新绑定数据,因此性能差。无效化体现在代码上即是为 ViewHolder 添加 FLAG_UPDATE 和 FLAG_INVALID 标志位

12.不管脱手还是跟手,真正滑动的地方:循环遍历子view,执行offsetTopAndBottom
RecyclerView 的脱手滚动(fling)是一段一段进行的,每一小段的滚动都被包裹在一个叫ViewFlinger的 Runnable 中。它会被抛到Choreographer中,作为动画任务暂存起来。待一下个垂直同步信号到来之时,被抛到主线程的消息队列中执行。

13.RecyclerView也把预拉取的实际工作委托给了一个名为GapWorker的类
UI线程空闲时才会执行,且必须在下一个Vsync信号到来之前
预加载的vh会缓存到cacheview中
https://juejin.cn/post/7181979065488769083

bitmap

1.bitmap内存 = 宽 x scale x 高 x scale x 每个像素字节大小
其中scale = 设备dpi / 对应文件夹dpi(scale针对图片)

2.px = dp x (dpi / 160)(此公式针对普通 dp px 转换)

3.bitmap内存优化:
options.inPreferredConfig = Bitmap.Config.RGB_565;//图片格式,一个像素占2字节
options.inSampleSize = i;//采样率,i不能小于1,隔i个像素采集一次数据
op.inMutable = true;op.inBitmap = reuseBitmap;//复用
匿名共享内存,5.0以上已经禁止
bitmap.compress(Bitmap.CompressFormat.JPEG, 20, 
new FileOutputStream(“sdcard/result.jpg”));//这种方式不好,容易失真

4.bitmap储存位置:
3.0 - 7.0 存在java堆
3.0之前,8.0及之后,存native堆

5.加载长图大图
推荐:Glide+SubsamplingScaleImageView
https://github.com/davemorrissey/subsampling-scale-image-view
https://juejin.cn/post/6955427322291814431

webview

1.webViewClient控制url跳转

2.webChromeClient可以处理网页加载进度

3.web.settings可以设置网页各项参数

4.想让当前webview加载通过,正确写法就是返回false:
public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
return false;
}
实测:android11,页面打开时不会调用shouldOverrideUrlLoading,但是点击当前页面内的超链接,会调用shouldOverrideUrlLoading

5.webview采用独立进程,避免占内存和各种未知兼容crash
【大多数 Android O 以上的设备webview默认是多进程模式运行的】

https://juejin.cn/post/6844903534958231559

io&okio

https://bbs.huaweicloud.com/blogs/353242
传统io采用装饰者模式,写法繁琐,其中有个 FilterInputStream 类涉及到 装饰者和继承的问题
如果没有FilterInputStream,靠继承,那么会出现数不清的子类,造成类爆炸
所以这里采用继承不是好方法,而是采用组合的方法

关键点:不是通过继承,容易造成爆炸,而是通过组合,每个子类对应一个功能,然后多个功能组合起来使用


BufferedInputStream原理

如果都是调用int read(byte b[], int off, int len)这个方法,特别是当b[]的大小真好是8192时FileInputStream和BuffereInputStream的性能可以说是一样的

所以我们常常说BuffereInputStream的效率比FileInputStream高,应该针对的是int read()这种每次只读取一个字节的方法。

BufferedInputStream 靠 FileInputStream 读取数据到缓冲区

使用FileInputStream的read方法,其中b数组大小为8k

1
2
3
4
5
6
7
public int read(byte b[], int off, int len) throws IOException {
if (closed && len > 0) {
throw new IOException("Stream Closed");
}
tracker.trackIo(len, IoTracker.Mode.READ);
return IoBridge.read(fd, b, off, len);
}

RandomAccessFile支持跳到文件任意位置读写数据,适合做断点续传


source对应inputstream
sink对应outputstream

1.okio很方便是因为不需要嵌套很多装饰类,BufferedSink和BufferedSource已经满足大部分功能

2.Segment字面翻译就是片段,大小8k,Okio将数据也就是Buffer分割成一块块的片段,同时segment拥有前置节点和后置节点,构成一个双向循环链表,而Segment内部的数据是数组,兼具读写和插入

3.SegmentPool ,segment的对象池,池子的上限是64k,相当于8个segment,其中有next和bytecount两个内部变量,next这个节点可以看出SegmentPool是按照单链表的方式进行存储的,byteCount则是目前已有的大小。
回收和取对象都是加锁的,SegmentPool无论是回收和取对象都是在表头操作。

4.ByteString,内部有两变量,byte和string,所有转换无压力,空间换时间

5.Buffer是okio整个读和写的核心,RealBufferedSource 和 RealBufferedSink 实际上都只是一个代理,里面的操作全部都是通过Buffer来完成的

6.日志系统涉及到的写入问题,可以用mmap技术,比io性能高,在Android中可以将文件通过Java提供的MappedByteBuffer映射到内存,然后进行读写。微信的xlog模块mmap实现是基于C++代码实现
MappedByteBuffer 位于 Java NIO 包下,用于将文件内容映射到缓冲区,使用的即是 mmap 技术

也可以通过JNI调用C++方法

okhttp

1.设计模式
1)责任链模式
2)建造者模式 Builder
3)工厂模式 Factory
工厂模式和建造者模式类似,区别就在于工厂模式侧重点在于对象的生成过程,而建造者模式主要是侧重对象的各个参数配置。
4)观察者模式 Okhttp中websocket的使用
5)单例模式

2.责任链:主要是RealInterceptorChain这个类,RealInterceptorChain中有个proceed方法,会不断取下一个拦截器进行处理,并且传入当前的RealInterceptorChain,进入拦截器处理后,
又会执行RealInterceptorChain的proceed方法,如此循环。。。可见proceed方法前后,是第一次拦截和第二次拦截,第二次拦截是递归回来了

3.OkHttp中使用ConectionPool实现连接池,默认支持5个并发KeepAlive,默认链路生命为5分钟。

4.ConnectInterceptor拦截器负责从ConectionPool中找可用连接,没有则会创建,还有个线程会定期清理

5.当正在执行的任务未超过最大并发64,同时同一 Host 并发不超过5个,则会添加到正在执行队列,同时提交给线程池。否则先加入等待队列。每个任务完成后,都会调用分发器的 finished 方法,这里面会取出等待队列中的任务继续执行

if (。。。) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}

activity&fragment

【结合viewpager使用的】

1.FragmentPagerAdapter 和FragmentStatePagerAdapter
FragmentPagerAdapter 中每一个Fragment都长存在与内存中,适用于比较固定的少量的Fragment。FragmentPagerAdapter 在我们切换Fragment过程中不会销毁Fragment,只是调用事务中的detach方法。而在detach方法中只会销毁Fragment中的View,而不会销毁Fragment对象。
FragmentStatePagerAdapter中实现将只保留当前页面,当页面离开视线后,就会被消除,释放其资源。而在页面需要显示时,生成新的页面。在较多的Fragment的时候为了减少内存可适用。FragmentStatePagerAdapter在我们切换Fragment,会把前面的Fragment直接销毁掉。

view绘制

ViewRootImpl:
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
(mView就是DecorView)

DecorView:
(DecorView继承 FrameLayout 继承ViewGroup继承View)
这里直接看View的measure方法,里面执行onMeasure
因为FrameLayout重写了onMeasure,所以进入查看


测量:
1.measure ——>
2.onMeasure ——>
3.在相应的实现类中的onMeasure方法内
执行for循环child.measure(wSpec,hSpec)
Spec = 自身Spec + child lp(这是对于当前View来说的)
如果对于child来说,那就是parentSpec + 自身lp ——>
4.setMeasuredDimension
(上述第三部是递归,递归结束才会回到第四部,正好由子View到父View)


布局:
layout过程和上述类似,从view出发,在相应的实现类中的onlayout方法内执行for循环

绘制:
1)绘制背景
2)如果需要,保存图层
3)绘制View内容(容器没有onDraw方法)
4)绘制子控件
5)如果需要,恢复图层
6)画装饰(滚动条等)

view事件分发

1.在fw层,事件分发是责任链模式
ViewRootImpl不仅负责界面的绘制,同时负责事件的传递。
ViewRootImpl.setView方法中组装责任链
点击事件发生在责任链的ViewPostImeInputStage阶段

2.L型链的特点是最终会有一个组件对事件进行消费。
如果没有组件消费,最终事件会重回Activity,变成U型链


(其实并不是U型,结合记事本内容查看)

3.dispatchTransformedTouchEvent:
1)传child=null时,调用自身消费
2)不为null时,分发给子view

4.onTouchListener 优先于 onClickListener
onTouch 优先于 onClick

5.确定down事件的消费对象mFirstTouchTarget后,
可以避免move和up事件【进行for循环查找子View的位置】这一步
比如:
A父布局
BCD同级子布局
D中一个子布局按钮获取点击事件
所以move和up不需要再次判断BC,直接找到D,再直接找到按钮

hook api 简单记录

比如hook Activity的Instrumentation变量

步骤类似:
1.获取变量A
2.讲A放入ProxyA中(其中ProxyA继承A,不影响A执行原生方法)
3.将A替换成ProxyA

public static void hook(Activity activity) {

try {

    Field mInstrumentation = Activity.class.getDeclaredField("mInstrumentation");
    mInstrumentation.setAccessible(true);
    Instrumentation instrumentation = (Instrumentation) mInstrumentation.get(activity);
    //上述三步可以拿到Instrumentation变量

    ProxyInstrumentation proxy = new ProxyInstrumentation(instrumentation);
    //将变量放入代理中

    mInstrumentation.set(activity, proxy);
    //替换变量

} catch (Exception e) {
    e.printStackTrace();
}

}

app架构

mvc:mvc三方互相耦合,代码混乱

mvp:vp耦合,pm耦合,但代码职责比mvc清晰

mvvm: viewModel不持有view ,view和vm通过livedata观察者模式联系

Binder

需结合记事本学习

mmap技术:
1.Server端在启动之后,对/dev/binder设备调用mmap。
2.内核中的binder_mmap函数进行对应的处理:申请一块物理内存A,然后在Server端的用户空间B和内核空间C同时进行映射
3.所以相当于BC相互映射,可以互相直接访问
【发消息需要拷贝数据,收消息使用mmap】

binder驱动负责远程引用和本地实体服务的转换

ServiceManager

1.binder_open打开binder设备文件/dev/binder,执行mmap

2.binder_become_context_manager将自己注册为servicemanager,引用为0,其他的Client可以通过这个0号引用

3.binder_loop循环等待+处理 Client 请求(Service注册请求+代理对象获取请求)

System Server

Looper.prepareMainLooper();//当前线程作为主线程

startBootstrapServices();//启动引导服务【ATMS,AMS,PMS】
startCoreServices();//启动核心服务【电量,应用等】
startOtherServices();//启动其他服务【IMS,WMS,网络位置相机等】

Looper.loop();//loop循环

1.设置各种参数
2.启动各种服务Service
3.开启Looper循环,等待Client端请求(即需要调用AMS WMS等)

服务之间是存在依赖关系的,所以服务的启动划分了8个阶段:0-100-480-500-520-550-600-1000

一部分Service由startService启动

一部分Service由通过ServiceManager.addService注册服务,可以供其他地方调用

AMS

主要是通过在System Server中使用ServiceManager.addService

AMS主要负责系统中四大组件的启动,进程管理

8.0以及之前:
handleLaunchActivity-performLaunchActivity(Activity#onCreate和Activity#onStart)
handleResumeActivity-performResumeActivity (Activity#onResume)

9.0开始使用事务模式:

TransactionExecutor#executeCallbacks
LaunchActivityItem
StartActivityItem
ResumeActivityItem

Instrumentation是什么,和ActivityThread是什么关系?

监控系统和应用之间的交互,一个进程对应一个Instrumentation
ActivityThread通过Instrumentation执行activity的生命周期
activity通过Instrumentation(转到AMS)跳转activity

Window

Window是窗口的抽象概念,PhoneWindow是其唯一实现,PhoneWindow中有一个顶级View , DecorView
Window是View的管理者(即使用WM操作View,最后是通过ViewRootImpl实现的)
View是Window的呈现

WindowManager是个接口,继承自ViewManager
#addView
#updateViewLayout
#removeView

WindowManager的实现类是WindowManagerImpl
WindowManagerImpl又交给了WindowManagerGlobal
WindowManagerGlobal内部使用了ViewRootImpl
ViewRootImpl#setView{
……
//1.绘制view
//requestLayout内部主要使用垂直同步信号VSync的方式,在收到GPU提供的VSync信号后,会触发View的三大绘制layout,mesure,draw流程
requestLayout();
……
//2.通过session与WMS建立通信:完成window的添加
//WindowManagerService的openSession可以获得session
mWindowSession.addToDisplay(mWindow……);
……
……
}

WMS

窗口管理:
1.窗口权限检查
2.窗口信息校对
3.获取对应WindowToken
4.窗口有效性检查
5.如果前面满足,则创建一个WindowState对象
6.通过WindowState的attach方法与SurfaceFlinger通信
7.SurfaceFlinger进行渲染输出

输入事件的中转站:
1.启动InputManagerService,同时会创建InputManager
2.创建InputManager同时创建InputReader和InputDispatcher
3.InputReader读取事件
4.InputDispatcher将事件分发给合适的Window
5.WMS会把所有窗口的信息更新到InputDispatcher中
6.Window传到DecorView,然后是android传统事件分发

Activity的Window创建
Dialog的window创建
Toast的window创建

都是通过wm.addView的方式来添加window的

IMS

物理触摸事件 经过 CPU处理 保存在 /dev/input/eventX 中

InputReader(内部有eventhub,监听内核中保存的事件,将事件放入InputDispatcher内部的eventqueue中)
InputDispatcher(内部有eventqueue)
InputDispatcher通过channel通道和应用层跨进程通信(双端socket通信)

每一次视图创建或者activity拉起,WMS都会通过InputMonitor将窗口集合同步到IMS的InputDispatcher中
所以InputDispatcher可以找到具体哪一个window来分发事件

PMS

1.加载管理应用程序
2.提供包和组件的信息
3.检查app权限
4.提供安装卸载的接口

管理包信息通过以下两个文件:
/system/etc/permissions.xml 和/data/system/packages.xml

所有系统程序的文件处于/system/app/目录下,
第三方程序文件处于/data/app/ 目录下,
在程序安装过程中PMS会将要安装的apk文件复制到/data/app/目录下

系统启动创建PMS
PMS读取配置文件
PMS扫描所有app
PMS更新配置文件

PackageInstaller 提供了 应用安装、更新、移除的能力,
当然具体实现是 IPC 到了 PackageInstallerService中。

安装方式

1.系统app和预置app(没有安装界面)
PackageManagerService的构造中会扫描对应目录下的apk,完成安装

2.官方市场app内安装(没有安装界面)
调用PackageManager的installPackage方法执行安装

3.PC 上 ADB安装(没有安装界面)
通过内置的pm脚本来执行PM.java

4.第三方应用安装(有安装界面)

安装流程

1.复制apk到/data/app,解压,PackageParser扫描
2.dex文件放入/data/dalvik-cache
3./data/data中创建包名文件夹,用来存放应用的数据库、xml文件、cache、二进制的so动态库等
4.PMS:解析apk的AndroidManifest.xml文件,注册四大组件,将apk各种信息保存在/data/system/packages.xml文件中
5.资源管理器解析APK里的资源文件。
6.dex2oat
7.发送安装完成广播

ActivityIntentResolver mActivities:
遍历所有程序的目录,并解析所有的注册清单文件,将提取 所有的Intent-filter数据保存在对应的集合中;
ActivityIntentResolver mReceivers:同上
ServiceIntentResolver mServices:同上
ProviderIntentResolver mProviders:同上

activity跳转时,会调用PMS获取对应activity的信息

优化

1.UI
x2c,asynclayoutinflater

2.启动

一般在创建进程以及之前都是无法干预的,所以优化方向就是 Application和Activity的生命周期 这个阶段

可以使用代码插桩的方式进行方法耗时统计

onWindowFocusChanged只是首帧时间,如果需要算上首页接口时间,可以用getViewTreeObserver()

可以通过Systrace来观察各个阶段的耗时

线程收敛:统一内部库的线程库

第三方库懒加载,针对于一些应用启动时不需要初始化的库,可以等到用时才进行加载。
异步启动器,延迟启动器(利用IdleHandler进行延时启动)
启动器总结:
能异步的task我们会优先使用异步启动器在Application的onCreate方法中加载,对于不能异步的task,我们可以利用延迟启动器进行加载。如果任务可以到用时再加载,可以使用懒加载的方式。

设置线程优先级

xml转view,x2c

方法内敛,类重排:facebook的Redex或者byteX

redex:1.通用字节码优化+dex优化

3.包体积

R8与混淆
R8:压缩比例大,kotlin友好
混淆:处理枚举,算法强,能优化gson
有了 R8,可以在一个步骤中完成脱糖、压缩、混淆、优化和 dex 处理 (D8),
在之前混淆中,混淆和D8是分开的,产生两遍class文件,效率低

AndroidResGuard:主要是通过 短路径的优化,以达到 减少 resources.arsc、metadata 签名文件以及 ZIP 文件大小 的效果
其实效果一般

开发流程注意点

研发方面:

1.技术方案:技术方案评审
2.SDK:多方面对 SDK 进行评估,同时要求三方提供稳定性报告。
3.架构优化:对基础功能进行封装,在基础库里做好统一容错等稳定性防护。

灰度阶段:

新版本上线采用多轮灰度 → 全量 的发布模式,在新版本全量发布前,充分灰度暴露问题,将影响范围降至最低
改动严格审核
Crash&ANR 及时监控和修复
收集用户反馈

运维阶段:

稳定性监控主要是通过 Bugly,可以自研一个监控bugly的脚本,第一时间通报钉钉
可以获取bugly数据,自己分析,归因

容灾:

服务端控制 是否 启用新功能,即服务端控制开关
有问题的页面 可以通过路由 拦截 (页面的跳转可以通过服务端配置来开关)

日志:

写入日志,高性能mmap
https://juejin.cn/post/6844904118088105991
上传日志
按策略上传,可以先存在用户本地,或者抽样上传

随记

1.跨进程大数据传输可以使用 imagewrite/reader
https://juejin.cn/post/7146148906059956232#comment

2.webview
shouldOverrideUrlLoading()方法不是每次加载都会调用,WebView的前进、后退等不会调用shouldOverrideUrlLoading方法;
非loadUrl方式加载 或者 是重定向的,才会调用shouldOverrideUrlLoading方法。