根据简历,面试可能会问的问题
架构模式:MVI、MVVM、MVP
- MVP:
- Luban图片压缩二次封装做了什么事情?项目中的图片压缩怎么做的--主要是使用Glide框架内部的压缩,Glide中图片压缩的原理是什么?
- 内存优化,项目中的内存泄漏是怎么治理的?内存泄漏率是怎么计算的
- 为什么要将MVP架构重构成MVVM架构?LifeCycle、ViewModel、LiveData
- 协程 Flow有使用过吗?原理是什么?两者的区别是什么?
-
LifeCycle感知组件生命周期的流程:
拿到lifeCycle调用addObserver添加LifeCycleObserver缓存到LifeCycleRegistry中的observerMap中,在ComponentActivity中通过没有界面的ReportFragment感知activity生命周期的变化调用dispatch方法通过activity实例强转拿到lifeCycle遍历observerMap中缓存的LifeCycleObserver调用onStateChanged方法分发生命周期。
-
MVP架构存在的问题:
- 逻辑业务presenter层通过大量的constact接口回调数据刷新视图层view,业务庞大复杂的时候会造成协议接口膨胀,维护难度增大。
- view视图层实现协议接口,presenter层持有协议接口的强引用,在presenter层处理异步任务的时候容易引起内存泄漏。
-
MVVM架构解决这两个问题:
- view视图层通过LiveData订阅数据结果,完成业务逻辑与视图层的解耦。LiveData具有生命周期感知能力,能够跟随生命周期管理资源释放,避免内存泄漏。
- viewModel业务逻辑层具备跟随组件生命周期保存数据的能力。
- 项目中的组件化是怎么搭建的?组件之间的通信是怎么实现的?ARouter的原理
- 网络框架使用的是什么?OkHttp的原理,任务的调度、连接池、复用池,Http各版本之间的差异,TCP/UDP通信以及数据分包?Retrofit的原理?
- H5秒开优化是怎么做的?秒开率怎么计算的?JS与原生之间如何通信,以及大数据怎么交互
- 项目中热修复的方案是什么?原理是什么?插件做过没,讲一讲你是怎么做的,字节码插桩等等
- 插件化换肤框架怎么实现的?View的绘制机制
- 事件分发机制,View的绘制机制、WMS窗口管理,Activity的启动流程
- 线程池并发编程,任务调度。多线程数据安全,以及多线程之间数据同步
- EventBus原理,缺陷,如何修复缺陷?
- 常用数据结构、原子类,CAS原理,AQS原理
- 跨进程通信
- Binder底层实现
- Handler原理
- App启动优化
- kotlin高阶函数,扩展函数编译后是什么样的,kotlin中单例模式怎么实现。协程、Flow
- GreenDAO、Room数据库
- SharedPreference的原理和缺陷、如何改进
- Flutter的运行机制,项目中是如何管理事件的?BloC,BloC的原理。Flutter Widget渲染原理,Flutter中如何并发编程 await async
- Lint规则怎么写
MVVM与MVP的区别
MVP:
- Presenter层级的业务结果通过协议的View接口回调到视图层。协议接口膨胀
- Presenter层持有View的引用,存在内存泄漏,不具备感知组件生命周期的能力。
MVVM
- 视图层通过订阅LiveData的方式观察数据的更新,LiveData具有组件生命周期感知能力,基于LifeCycle实现
- 解决协议接口膨胀和不具备组件生命周期感知能力
MVVM的不足
- 为保证对外暴露的
LiveData
是不可变的,需要添加不少模板代码并且容易遗忘 View
层与ViewModel
层的交互比较分散零乱,不成体系
MVI
什么是MVI? Model:管理状态 View:承载视图 Intent:承载用户的意图 状态是DataClass
MVI有什么不同?解决的什么问题? 通过状态的单向流通,解决了 LiveData对外暴露不可变的模版代码和View与ViewModel的分散交互
RecyclerView局部刷新DiffUtils
Lifecycle
Lifecycle使用:
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
Log.d(TAG,event.toString())
}
})
涉及的重要角色:
LifecycleRegistry extends Lifecycle
LifecycleRegistry: 持有存储LifecycleEventObserver的集合mObserverMap,通过addObserver添加LifecycleEventObserver到mObserverMap缓存,当生命周期改变的时候会调用handleLifeCycleEvent()处理分发生命周期,拿到缓存的LifecycleEventObserver回调onStateChanged
public interface LifecycleOwner {
@NonNull
Lifecycle getLifecycle();
}
LifeCycle原理:
感知生命周期的能力时借助ReportFragment(一个透明的Fragment)
分发生命周期回调事件(LifeCycle.Event)。
LifeCycle感知组件生命周期的流程:
拿到lifeCycle调用addObserver添加LifeCycleObserver缓存到LifeCycleRegistry中的observerMap中,在ComponentActivity中通过没有界面的ReportFragment感知activity生命周期的变化调用dispatch方法通过activity实例强转拿到lifeCycle遍历observerMap中缓存的LifeCycleObserver调用onStateChanged方法分发生命周期。
LiveData
基本使用
//数据层
val mData = MutableLiveData<String>()
//视图层
mData.observe(this, Observer{ mData-> nameTextView.text = newName})
mData.setValue("a")
mData.postValue("b")
基本原理
观察者模式,对LiveData数据添加一个或者多个观察者,在数据改变的时候,组件生命活跃状态下,实现数据的订阅和分发。
常见问题
LiveData
如何实现订阅者模式,如何处理发送事件?
自身通过注册观察者,在数据更新时进行数据变更的通知;
LiveData
怎么做到生命周期的感知,如何跟LifecycleOnwer
绑定?
通过将Observer
和LifecyclerOnwer
包装成新的对象LifecycleBoundObserver
,接收生命周期改变的状态来实现对生命周期的感知。
LiveData
只在LifecycleOwner
的active
状态发送通知,是怎么处理的?
非活跃状态数据无法感知生命周期变化,同时在considerNotify
时,会判断包装后的观察者是不是活跃状态来决定数据的分发。
LiveData
会自动在DESTROY
的状态下取消订阅,是怎么处理的?
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
接收到Lifecycle
的DESTROYED
状态,会从订阅的map中移除observer来取消订阅。
- 生命周期变化后数据处理流程是怎么样的?
onStateChanged ——> activeStateChanged ——> dispatchingValue ——> considerNotify ——> onChanged
- 为什么观察者只能与一个
LifecycleOwner
绑定,而不是多个?
绑定多个的话,LiveData的生命周期感知能力就乱掉了,会有很多问题;
- LiveData的粘性事件?
之前的Observer
已经订阅并更新数据,mVersion
与mLastVersion
不再保持同步那么再新增一个Observer
,他也会立即受到旧的消息通知。
LiveData的特点
-
实时数据刷新:当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据;
-
不会发生内存泄漏:observer会在LifecycleOwner状态变为DESTROYED后自动remove;
-
不会因 Activity 处于STOP等状态而导致崩溃:如果LifecycleOwner生命周期处于非活跃状态,则它不会接收任何 LiveData事件;
-
不需要手动解除观察:开发者不需要在onPause或onDestroy方法中解除对LiveData的观察,因为LiveData能感知生命周期状态变化,所以会自动管理所有这些操作;
-
数据始终保持最新状态:数据更新时,若LifecycleOwner为非活跃状态,那么会在变为活跃时接收最新数据。例如,曾经在后台的 Activity 会在返回前台后,observer立即接收最新的数据等;
LiveData数据倒灌
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
原理:if (observer.mLastVersion >= mVersion) { return; }
在生命周期组件重建时,ViewModel会恢复activity销毁之前的数据并与新建的activity关联,LiveData
的mVersion
变量的值也会恢复,但在新建的activity会重新调用LiveData
的observe()
方法添加新的observer
,LiveData
的mLastVersion
的值会重置。activity由非活跃状态变为活跃状态的时候LiveData会发起数据的分发,这时候就会发生数据倒灌。
解决方案: 自定义一个LiveData,使用AtomicBoolean标记事件分发的状态,在数据更新的时候设置AtomicBoolean标记为true,事件分发后设置为false,保证每次数据只做一次分发的消费。
基本原理:
观察者模式,对LiveData
数据添加一个或者多个观察者,实现数据的订阅与分发;
主要成员:
- mVersion:LiveData实例的版本标记,创建LiveData以及setValue时会进行更新+1;
- mObservers:存储Observer包装类型实例的map;
- mData:使用LiveData保存的需要观察的数据;
- mPendingData:保存LiveData临时数据的变量,mPendingData == NOT_SET第一次一定是返回true,之后都是返回false,然后到这个值更新完毕之前的会调用mPendingData=NOT_SET,这也是为什么多次调用 postValue()只有最后一个值才有效的原因;
ViewModel
ViewModel 的作用是专门存放与界面相关的数据,分担 Activity
/Fragment
的逻辑,同时会维护自己独立的生命周期
特点:
- 单一职责,将数据从业务中抽离出来。即只要是界面上看的到的数据,相关变量都应该存放在ViewModel,而不是Activity中。
- 生命周期长,存在于所属对象(Activity,Fragment)的全部生命周期,在Activity横竖屏重建时不会被销毁。
应用场景:
- 横竖屏切换,Activity重建,数据可依然保存
- 同一个Activity下,Fragment之间的数据共享
创建方式:
-
val viewModel = ViewModelProvider(this).get(TestViewModel::class.java)
-
val viewModel by viewModels()
上述两种方式最终都是基于
ViewModelProvider.Factory
来生成 ViewModel 实例。
生命周期:
ViewModel 目前只有一个生命周期方法 onCleared()
,是在 ViewModel 实例对象被清除的时候回调。
ViewModel实现原理:
- Activity(Fragment) 的 ViewModel 都存储在 ViewModelStore 中,每个 Activity(Fragment) 都会拥有一个 ViewModelStore 实例
- ViewModelProvider 负责向使用者提供访问某个 ViewModel 的接口,其内部会持有当前 Activity(Fragment) 的 ViewModelStore,然后将操作委托给 ViewModelStore 完成
- ViewModel 能在 Activity(Fragment) 在由于配置重建时恢复数据的实现原理是:Activity(指 support library 中的 ComponentActivity) 会将 ViewModelStore 在 Activity(Fragment) 重建之前交给 ActivityThread 中的 ActivityClientRecord 持有,待 Activity(Fragment) 重建完成之后,再从 ActivityClientRecord 中获取 ViewModelStore
- 如果应用的进程位于后台时,由于系统内存不足被销毁了。即使利用 ViewModel 的也不能在 Activity(Fragment) 重建时恢复数据。因为存储 ViewModel 的 ViewModelStore 是交给 ActivityThread 中的 ActivityClientRecord 暂存的,进程被回收了,ActivityThread 也就会被回收,ViewModelStore 也就被回收了,ViewModel 自然不复存在了
activity因为配置重建不会被销毁的源码:
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
组件化工程建设
示例鉴赏:
组件化开发的意义
1. 各个组件专注自身功能的实现,模块中代码高度聚合,只负责一项任务,符合单一职责原则
2. 各组件高度解耦,各业务研发职责清晰、互不干扰、提升协作效率
3. 业务组件可进行拔插,灵活多变,复用性高
4. 加快编译速度,提高开发效率
1. 你们的组件化是怎样做的?
按照业务、功能划分,最底层是通用的基础功能组件(不包含任何业务),例如:OkHttp、
Retrofit、Glide、日志、权限、通用工具类等;往上层是基础业务组件,例如:登录组件、
分享组件、支付组件、广告组件等;再往上层是业务组件,一般按照业务线划分;最上层是App
壳工程,用来集成各业务组件组成完整业务功能的应用。业务组件都是同级,不存在依赖关系。
## 组件化方案分为两种:
1. 单工程方案
application主module和集成的各业务library module,独立调试需要通过开关动态配置。
2. 多工程方案
各业务组件工程以aar的方式集成到App壳工程,独立调试是在各业务组件的工程里边单独调试。
## 单工程方案和多工程方案对比:
单工程方案各组件无法做权限隔离,多工程可以。
2. 如何定义module的执行顺序?
通过定义build.gradle Task依赖的顺序执行
3. 组件代码本地依赖和远程依赖
通过开关改变localMaven和maven的依赖实现
组件化中常见的问题
## 1.业务组件,如何实现单独运行调试?
单工程通过gradle.properties文件中定义开关敞亮来实现;多工程在业务组件测试入口独立构建编译。
## 2.业务组件间 没有依赖,如何实现页面的跳转?
通过路由框架实现,ARouter、WMRouter、TheRouter
## 3.业务组件间 没有依赖,如何实现组件间通信/方法调用?
通过组件暴露的接口服务实现组件间通信/方法调用。
例如:A组件要调用B组件的方法,B组件衍生出一个对外暴露调用接口服务的组件export_B(桥接转换作用),
export_B定义一些对外需要的服务接口,B组件依赖export_B组件实现定义的接口功能,A组件依赖export_B
组件,通过ARouter注入调用(ICartService) ARouter.getInstance().build(CartRouterTable.PATH_SERVICE_CART)
.navigation()拿到服务的实例实现组件间的通信。
## 4.业务组件间 没有依赖,如何获取fragment实例?
通过ARouter获取Fragment userFragment = (Fragment) ARouter.getInstance()
.build(CartRouterTable.PATH_FRAGMENT_CART).navigation()
## 5.业务组件不能反向依赖壳工程,如何获取Application实例、如何获取Application onCreate()回调(用于任务初始化)?
方案一:通过AppLifeCycle插件 参看博文:[组件化改造]
方案二:面向接口编程 + 反射扫描实现类: 该方案是基于接口编程,自定义 Application
去实现一个自定义的接口(interface),这个接口中定一些和 Application 生命周期相对
应的抽象方法及其他自定义的抽象方法,每个组件去编写一个实现类,该实现类就类似于一个假
的自定义 Application,然后在真正的自定义 Application 中去通过反射动态查找当前
运行时环境中所有该接口的实现类,并且去进行实例化,然后将这些实现类收集到一个集合中,
在 Application 的对应声明周期方法中去逐一调用对应方法,以实现各实现类能够和
Application 生命周期相同步,并且持有 Application 的引用及 context 上下文对象,
这样我们就可以在组件内模拟 Application 的生命周期并初始化SDK和三方库。使用反射还
需要做一些异常的处理。该方案是我见过的最常见的方案,在一些商业项目中也见到过。
【更多思路,请参看博文:基于MVVM组件化】
插件化(动态化配置)
Gradle构建
Plugin
开源框架
ARouter
## 主要的使用场景
1. 组件间页面跳转
2. 组件间方法调用
3. 跨组件Fragment实例获取
4. 页面携参跳转赋值
原理
核心Api调用: ARouter.getInstance().build(PathManager.SAY_HELLO).navigation()
在编译期间会调用注解处理器的子类执行process方法,在相应的注解处理器中通过javaPoet
生成Route、Provider等辅助类,这些类会被打包到源码中,在init初始化的时候通过DexFile
加载Dex遍历class,通过com.alibaba.android.arouter.routes包名过滤出辅助类,将
类名添加到Map并缓存到本地SP。 遍历类名map,通过类名后缀分别加载到路由映射表groupsIndex、
interceptorsIndex、providersIndex。 在调用ARouter.getInstance().build().navigation()
的时候,构造PostCad,在LogisticsCenter的completion流程中通过PostCard的path、type
实例出相应的对象(Provider),然后添加providers缓存。判断是否绿色通道isGreenChannel,
决定是否执行拦截器。_navigation()中根据PostCard携带的类型执行不同的逻辑:activity构造
Intent跳转;provider获取PostCard携带的provider实例返回;fragment/broadcast 实例后返回。
autoWires:在inject(this)的时候会获取AutowiredService(ARouter预埋的服务)调用doInject流程中
通过全类名实例化ISyringe接口的实现类(为autoWires生成的辅助类),然后调用inject为autoWires修饰的
变量赋值。
OkHttp详解
基本使用
OkHttpClient okHttpClient = new OkHttpClient.Builder() .build();
Request request = new Request.Builder() .url(url) .build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
}
@Override public void onResponse(Call call, Response response) throws IOException {
}
});
请求与响应流程
1. 请求的封装
final class RealCall implements Call {
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
//我们构建的OkHttpClient,用来传递参数
this.client = client;
this.originalRequest = originalRequest;
//是不是WebSocket请求,WebSocket是用来建立长连接的,后面我们会说。
this.forWebSocket = forWebSocket;
//构建RetryAndFollowUpInterceptor拦截器
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
}
request请求以及参数将会被封装成realCal
2. 请求的发送
- 异步请求:构造一个AsyncCall,并将自己加入处理队列中
final class RealCall implements Call {
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
}
- 同步请求:直接执行,并返回请求结果
final class RealCall implements Call {
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
}
3. 请求的调度
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
synchronized void enqueue(AsyncCall call) {
//正在运行的异步请求不得超过64,同一个host下的异步请求不得超过5个
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
}
- readyAsyncCalls:准备运行的异步请求
- runningAsyncCalls:正在运行的异步请求
- runningSyncCalls:正在运行的同步请求
Dispatcher:任务调度器
同步请求: 将请求realCall
添加到正在运行的同步请求队列runningSyncCalls
异步请求: 在请求realCall
添加前判断当前正在执行的异步请求总数量是否超过64,并且
同一个host下的异步请求是否超过5个。如果超过将请求任务添加到等待运行的异步请求队列readyAsyncCalls
4. 请求的处理
final class RealCall implements Call {
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//这里可以看出,我们自定义的Interceptor会被优先执行
interceptors.addAll(client.interceptors());
//添加重试和重定向烂机器
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
}
调用getResponseWithInterceptorChain()
将请求任务传递到拦截器链上发起请求任务的处理过程。
拦截器
1. RetryAndFollowUpInterceptor:负责重定向
2. BridgeInterceptor:负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应
3. CacheInterceptor:负责读取缓存以及更新缓存
4. ConnectInterceptor:负责与服务器建立连接
5. CallServerInterceptor:负责从服务器读取响应的数据
连接机制
1. 建立连接
connectInterceptor用来完成连接。而真正的连接在RealConnect中实现,连接由连接池`ConnectPool`来管理,
连接池最多保持5个地址的keep-alive连接,每个keep-alive时长为5分钟,并有异步线程清理无效的连接。
主要由以下两个方法完成连接:
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
最终调用`findConnect()`方法完成连接的创建
一. 建立连接的主要流程
1. 查找是否有完整的连接可用:
完整可用连接的条件:
1.1 Socket没有关闭
1.2 输入流没有关闭
1.3 输出流没有关闭
1.4 Http2连接没有关闭
2. 连接池中是否有可用的连接,如果有则复用
3. 如果没有可用连接,则创建一个新的连接
4. 开始TCP连接以及TLS握手操作
5. 将新创建的连接加入连接池
上述方法完成后会创建一个RealConnection对象,然后调用该方法的connect()方法建立连接。
2. 连接池
我们知道在负责的网络环境下,频繁的进行建立Sokcet连接(TCP三次握手)和断开Socket(TCP四次分手)是非常消耗网络资源
和浪费时间的,HTTP中的keepalive连接对于降低延迟和提升速度有非常重要的作用。Okhttp支持5个并发KeepAlive,默认
链路生命为5分钟(链路空闲后,保持存活的时间),连接池有ConectionPool实现,对连接进行回收和管理。
public final class ConnectionPool {
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
//清理连接,在线程池executor里调用。
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
//执行清理,并返回下次需要清理的时间。
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
//在timeout时间内释放锁
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
}
ConectionPool在内部维护了一个线程池,来清理链接,清理任务由cleanup()方法完成,它是一个阻塞操作,首先执行清理,并返回下次需要清理的间隔时间,调用wait()方法释放锁。等时间到了以后,再次进行清理,并返回下一次需要清理的时间,循环往复.
cleanup()清楚连接的流程:
1. 查询此连接内部的StreanAllocation的引用数量。
2. 标记空闲连接。
3. 如果空闲连接超过5个或者keepalive时间大于5分钟,则将该连接清理掉。
4. 返回此连接的到期时间,供下次进行清理。
5. 全部都是活跃连接,5分钟时候再进行清理。
6. 没有任何连接,跳出循环。
7. 关闭连接,返回时间0,立即再次进行清理。
判断闲置连接的方式: 在连接RealConnection里有个StreamAllocation虚引用列表,每创建一个StreamAllocation,就会把它添加进该列表中,如果流关闭以后就将StreamAllocation对象从该列表中移除,正是利用这种引用计数的方式判定一个连接是否为空闲连接。
## 缓存机制
1. 缓存策略
强制缓存:需要服务端参与判断是否继续使用缓存,当客户端第一次请求数据是,服务端返回了缓存的过期时间
(Expires与Cache-Control),没有过期就可以继续使用缓存,否则则不适用,无需再向服务端询问
对比缓存:需要服务端参与判断是否继续使用缓存,当客户端第一次请求数据时,服务端会将缓存标识
(Last-Modified/If-Modified-Since与Etag/If-None-Match)与数据一起返回给客户端,
客户端将两者都备份到缓存中 ,再次请求数据时,客户端将上次备份的缓存 标识发送给服务端,服务端根据
缓存标识进行判断,如果返回304,则表示通知客户端可以继续使用缓存
2. 缓存管理
OkHttp缓存机制是基于DiskLruCache做的。Cache类封装了缓存的实现,实现了InternalCache接口。
public interface InternalCache {
//获取缓存
Response get(Request request) throws IOException;
//存入缓存
CacheRequest put(Response response) throws IOException;
//移除缓存
void remove(Request request) throws IOException;
//更新缓存
void update(Response cached, Response network);
//跟踪一个满足缓存条件的GET请求
void trackConditionalCacheHit();
//跟踪满足缓存策略CacheStrategy的响应
void trackResponse(CacheStrategy cacheStrategy);
}
Retrofit
EventBus
GreenDAO
Glide
Android全埋点方案
性能优化
WebView加载H5提速
参考策略
一. 离线包
1. 精简并抽取公共的 JS 和 CSS 文件作为通用的页面模板,打包时放置在客户端中。内置版本号,在app后台静默更新。
2. 合并多次IO,WebView 会在加载完主 HTML 之后才去加载 HTML 中的 JS 和 CSS 文件,先后需要进行多次 IO 操作,我们可以将 JS 和 CSS 还有一些图片都内联到一个文件中,这样加载模板时就只需要一次 IO 操作,也大大减少了因为 IO 加载冲突导致模板加载失败的问题。
二. 正文数据预请求
在页面跳转拦截中做正文数据的预请求。
三. 模版预加载
本地有缓存H5页面模版的情况下,可以让webView提前加载模版预热。
四. 延迟执行非首屏渲染操作
对于一些非首屏必需的网络请求、 JS 调用、埋点上报等,都可以后置到首屏显示后再执行。
五. 静态页面直出
并行请求正文数据虽然能够缩短总耗时,但还是需要完成解析 JSON、构造 DOM、应用 CSS 样式等一系列耗时操作,最终才能交由内核进行渲染上屏,此时 **组装 HTML** 这个操作就显得比较耗时了。为了进一步缩短总耗时,可以改为由后端对正文数据和前端代码进行整合,直出首屏内容,直出后的 HTML 文件已经包含了首屏展现所需的内容和样式,无需进行二次加工,内核可以直接渲染。其它动态内容可以在渲染完首屏后再进行异步加载
由于客户端可能向用户提供了控制 WebView 字体大小,夜间模式的选项,为了保证首屏渲染结果的准确性,服务端直出的 HTML 就需要预留一些占位符用于后续动态回填,客户端在 loadUrl 之前先利用正则匹配的方式查找这些占位字符,按照协议映射成端信息。经过客户端回填处理后的 HTML 内容就已经具备了展现首屏的所有条件
六. H5页面的图片格式采用WebP
七. DNS优化
最好就是保持客户端整体 API 地址、资源文件地址、WebView 线上地址的主域名都是一致的。
八. CDN加速
CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率
通过将 JS、CSS、图片、视频等静态类型文件托管到 CDN,当用户加载网页时,就可以从地理位置上最接近它们的服务器接收这些文件,解决了远距离访问和不同网络带宽线路访问造成的网络延迟情
Lint编码规范优化
Lint实践
Issue:用来声明一个Lint规则。
Detector:用于检测并报告代码中的Issue,每个Issue都要指定Detector。
Scanner:用于扫描并发现代码中的Issue,每个Detector可以实现一到多个Scanner。
IssueRegistry:Lint规则加载的入口,提供要检查的Issue列表。
检查时机
1. 编码实时检查
2. 编译时检查
3. commit时检查
4. CI中检查
数据结构
多线程
项目中实际案例
1. 富文本编辑器
2. 评论列表优化
3. Feed流视频播放优化
应用开发常用的一些方案
屏幕适配方案:AndroidAutoSize:JessYan 大佬的 今日头条屏幕适配方案终极版
热修复:
数据库存储GreenDAO:
Framework
AMS
主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度
WMS
用于窗口管理,由SystemService启动。在WMS没有运行之前,开机动画由BootAnimation直接通过OpenGL ES
与SurfaceFlinger的配合来完成。
PMS
负责各种APK的安装,卸载,优化和查询。
PMS 启动流程
简单的将其分为以下几个步骤
- SystemServer通过PMS的main方法启动PMS
- PMS构造方法中初始化Settings
- 扫描各个系统目录下APP相关信息
具体步骤
1.在SystemServer中启动PackageManagerService.main
2.newPackageManagerService()并添加到ServiceManager中
3.newinstaller(建立installer和installd的socket连接,最终在底层实现install,
remove,dexopt等功能)
4.通过systemConfig获取到xml文件中信息
5.创建PackageHandler Message Looper(安装/卸载请求)
6.readPLW()(读取data/system/packages.xml)
7.检查/system/etc/permissions/platform.xml中定制的library及/system/framework下的jar档案是否需要
dex提取进行优化。若需要优化,会调用installer.dexopt提取dex到/data/dalvik-cache中
8.调用scanDirLI扫描存放apk的各个目录
9.系统应用安装—通过scanPackageLI逐一解析AndroidManifest.xml文件,建立每个apk的配置
结构,并将apk配置信息添加到全局列表进行管理。
10.调用writeLPr()从全局列表中将apk信息写到packages.xml和packages.list中。
C++
多线程
NDK
JNI开发
打包so文件
通过gradle.build配置打包出.so文件
debug打包:Debug 通常称为调试版本
,通过一系列编译选项的配合,编译的结果通常包含调试信息,而且不做任何优化,以为开发人员提供强大的应用程序调试能力
。
release打包:Release通常称为发布版本
,是为用户使用的,一般客户不允许在发布版本上进行调试。所以不保存调试信息,同时,它往往进行了各种优化,以期达到代码最小和速度最优
。为用户的使用提供便利。
Flutter
Flutter开发
Flutter常见面试题
1. Flutter有几种Widget,有什么区别?
StatelessWidget: 无状态的widget,例如:title、icon
StatefulWidget: 有状态的widget,创建时需要指定一个state,在需要UI更新调用
setState(),在回调中改变一些变量数值等,组件会重新build以达到数显状态/UI的效果。
常见的状态管理方式:
1. widget自己管理自己状态
2. 父widget管理子widget的状态
3. 混合管理
决定状态管理的原则:
1. 有关用户数据由父 widget 管理
2. 有关界面效果由 widget 本身管理
3. 状态被不同 widget 共享,由他们共同的父 widget 管理
state中包含两个常用属性:
widget和context。widget属性表示当前正在关联的widget实例,但关联关系可能会在widget
重构时发生变化(framework会动态设置widget属性为最新的widget对象)。context属性是
buildContext类的实例,表示构建widget的上下文,每个widget都有一个自己的context对
象,它包含了查找、遍历当前widget树的方法。
**State生命周期:**
在StatefulWidget调用`createState`之后,框架将新的状态对象插入树中,然后调用状态对
象的initState。子类化State可以重写`initState`,以完成仅需要执行一次的工作。 例如,
您可以重写`initState`以配置动画或订阅platform services。`initState`的实现中需要
调用`super.initState`。
当一个状态对象不再需要时,框架调用状态对象的`dispose`,您可以覆盖该`dispose`方法来执
行清理工作。例如,您可以覆盖`dispose`取消定时器或取消订阅platform services。
`dispose`典型的实现是直接调用`super.dispose`。
1. initState:当前 widget 对象插入 widget树 中时调用
2. didChangeDependencies:当前 State 对象的依赖项发生变化时调用
3. build:绘制当前界面布局时调用
4. reassemble:使用热重载时调用
5. didUpdateWidget:widget 配置内容有变动重构时调用
6. deactivate:当前 widget 对象从 widget 树中移出时调用
7. dispose:当前 widget 对象从 widget 树中永久删除时调用
事件流是“向上”传递的,而状态流是“向下”传递的
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
···
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
Flutter入门笔记:
Flutter调用JNI的方式
Flutter相机插件开发
Flutter模块化/组件化的搭建
加密
Android数据加密
1. Android Apk打包时的混淆、加固(例如:360加固、爱加密)
Https
对称加密
服务端和客户端都持有密匙,服务端通过密匙加密明文传递给客户端,客户端获取加密后的信息,
用对称密匙解密信息。
优势: 加密速度快
缺点: 密匙的传递是个问题,容易被截取,密匙一旦被截取后, 就能轻易破解信息
常见的对称加密算法有AES、DES、3DES、TDEA、Blowfish、RC5和IDEA。
非对称加密
非对称加密中,服务端和客户端都有自己的公钥和私钥。公钥是公开的,私钥是私有的,私钥需要保密的。
这套公钥和私钥的有个两种加密解密流程。
公钥: 用公钥加密的信息,用私钥才能解密。因为私钥是私有的, 这种流程用于信息的加密解密.
私钥: 用私钥加密信息,用公钥来解密。因为公钥是共有的,这种流程用于认证。
常用的非对称加密算法有RSA、Elgamal、Rabin、D-H、ECC(椭圆曲线加密算法)等。
Https的SSL/TSL协议
SSL: secure socket layer 安全套接层协议。为网络通信提供安全及数据完整性的一种协议。
TSL: transport security layer 安全传输层协议。为网络间通信提供的一种安全协议。
Https:兼顾了安全和效率,同时使用了对称加密和非对称加密。
Https的通信流程: 服务端生成一对公钥和私钥,将自己的公钥、域名、有效期的信息提交到公开
的CA机构注册CA证书,CA通过私钥对服务端的公钥、域名等信息进行加密,然后CA证书颁发给服务
端,服务端将CA证书下发给客户端,客户端遍历系统内置的公开权威机构的CA公钥对CA证书进行配
对,配对成功进行解密拿到服务端的公钥,然后客户端生成一个随机值,使用公钥对随机值加密发
送到服务端,服务端通过私钥解密拿到随机值(对称密钥),后续通过这个随机值对称加密明文完
成后续的对称加密传输。