Android 基础
Activity 生命周期
Android Activity生命周期
onCreate,onStart,onResume,onPause,onStop,onDestroy,onRestart
注意,旧 Activity 先 onPause,新 Activity 才会 onCreate
异常情况下(旋转屏幕或低内存被杀),Activity 被回收,系统会调用 onSaveInstanceState,时机在 onStop 之前,重新创建后,会调用 onRestoreInstanceState(onStart 之后)
如果配置了 configChanges, 则会调用 onConfigurationChanged
Activity 启动模式
- standrard
- singleTop
- singleTask — 找寻 TaskAffinity 指定的任务栈
- singleInstance—具有singleTask的特性,另外该类型 Activity 会单独位于一个特殊的任务栈
Android 创建多进程
清单文件声明 process=”:remote”
Service使用方式,绑定和start
多次startService
D/lpy: onCreate
D/lpy: onStartCommand
D/lpy: onStartCommand
D/lpy: onStartCommand
D/lpy: onStartCommand
多次 bindService
onBind null onServiceConnected // 只调用一次
先 start 再 bind,然后 stop 再 unbind
onCreate onStartCommand onBind null onServiceConnected onUnbind // 这是 unbind 时的输出,调用 stop 没有任何输出 onDestroy
先 bind 再 start,然后 stop 再 unbind
onCreate onBind null onServiceConnected onStartCommand onUnbind // unbind 输出 onDestroy
先 start 再 bind,然后 unbind 再 stop
onCreate onStartCommand onBind null onServiceConnected onUnbind // unbind onDestroy // stop
先 bind 再 start,然后 unbind 再 stop
onCreate onBind null onServiceConnected onStartCommand onUnbind // unbind onDestroy // stop
Android 的 IPC 方式
- AIDL
- Bundle
- 文件
- Messenger
- ContentProvider
- Socket
RecyclerView与ListView(缓存原理,区别联系,优缺点)
ListView缓存分为ActiveView(屏幕中)和ScrapView(移出屏幕,脏)
RecyclerVIew缓存分为Scrap(屏幕中),Cache(移出屏幕,默认2),ViewCacheExtesion(自定义),RecyclerViewPool(需要重新绑定,脏)
handler原理?
首先在 Looper.parpare() 中,会在 ThreadLocal 中放入 Looper 对象,new handler 的时候,通过 ThreadLocal 找到当前 Thread 的 looper 对象,而在 Looper 中包含了一个 MessageQueue,这样一来,一个 Handler 对象包含一个 Looper 对象,而 Looper 对象又唯一对应一个 MessageQueue。在子线程中,通过 handler 发送消息,这个消息会根据触发时间插入到这个 Handler 的 MessageQueue (单链表)中。最后在 Looper.loop() 方法中开启无限循环,不断从 MessageQueue 中调用 next() 方法取新的 msg 发送给 handler 的 dispatchMessage。
注意
dispatchMessage的顺序:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
IdleHandler 用法原理?
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
//你要处理的事情
return false;
}
});
MessageQueue 中每次分发完 msg,都会判断 idleHandler[] 是否有值,有的话,就会调用
Looper怎么退出?
handler内存泄漏的原因?如何避免?
handler 内部消息没弄完,内部类持有外部类引用导致泄漏
HandlerThread
HandlerThread 继承自 Thread,内部 run 方法封装了一个 handler
IntentService
IntentService 继承自 Service,内部封装了一个 handler 和 HandlerThread,也就是说 IntentService 内部有一个线程在不断地执行发送过来的消息
View相关
自定义View
Android 动画分类
View 动画 — res/anim/xxx.xml
- TranslateAnimation —
- ScaleAnimation —
- RotateAnimation —
- AlphaAnimation —
- AnimationSet —
使用方法: Animation anim = AnimationUtils.loadAnimation(); mBtn.startAnimation(anim);
- TranslateAnimation —
帧动画 — res/drawable/xxx.xml
- AnimationDrawable —
使用方法: mBtn.setBackgroudResource(R.drawable.xxx); AnimationDrawable drawble = (AnimationDrawable) mBtn.getbackground(); drawable.start();
- AnimationDrawable —
属性动画 — res/animator/xxx.xml
- ValueAnimator
- ObjectAnimator
- AnimatorSet
插值器 Interpolator
根据系统给出的 input,计算相应的执行百分比(0 ~ 1)
valueAnimator.setInterpolator { input: Float ->
input
}
估值器 TypeEvaluator
插值器算出来的百分比作为估值器的参数
valueAnimator.setEvaluator(object : TypeEvaluator<Float> {
override fun evaluate(fraction: Float, startValue: Float, endValue: Float): Float {
return (startValue + fraction * (endValue - startValue))
}
})
监听 AnimatorUpdateListener / AnimatorListener()
Activity 切换动画(View动画的一种形式)
在 startActivity 或者 finish 之后,调用 overridePendingTransition(anim1, anim2);
事件分发流程?手指按下btn然后不抬手移出btn,此时事件如何传递?源码是如何判断的?
mFirstTouchTarget(单链表)是成功处理事件的子View
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev); // DOWN事件清除所有标记
resetTouchState();
}
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 如果 DOWN 事件 或者 子View 能处理某事件 进入
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
}
}
if (!canceled && !intercepted) { // 不取消,不拦截
if (actionMasked == MotionEvent.ACTION_DOWN) {
for (int i = childrenCount - 1; i >= 0; i--) {
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//分发给子View
//如果子View能处理,mFirstTouchTarget 就被赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
}
}
}
}
if (mFirstTouchTarget == null) {
// 没有子View可以处理,那么调用super.dispatchTouchEvent(),即会调用自己的 onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 有子View可以处理,那么分发给子View
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 如果是取消或者抬起事件,直接重置变量
resetTouchState();
}
}
滑动冲突如何处理?
外部拦截
- 重写 onIntercepTouchEvent
- DOWN 不拦截
- MOVE 在需要的时候拦截
- UP 不拦截
- 内部拦截
- 父 ViewGroup 拦截非 DOWN 事件
- 子 View 在 MOVE 事件中进行判断,如果父 ViewGroup 需要事件,则调用 requestDisallowInterceptTouchEvent(false),让父 ViewGroup 拦截
RequestLayout
requestLayout 会委托 parent 去刷新,因为子View改变会影响父ViewGroup的布局,所以 A—B—C,B 调用 requestLayout,AB会重新刷新,但是C不会
Window和WindowManager和ViewRootImpl
Window 是抽象类,唯一实现类是 PhoneWindow
WindowManager 是接口,唯一实现类是 WindowManagerImpl
Activity 包含一个 PhoneWindow,而 PhoneWindow 又包含一个 WindowManagerImpl 和 DecorView,WindowManagerImpl 中有委托给 WindowManagerGlobal 去实现,最后在 WindowManagerGlobal 的 addView 方法中,new 一个 ViewRootImpl,然后把 DecorView 传给 ViewRootImpl,完成一个 View 树的创建
同理,Dialog,Toast 内部也是如此,每一个Activity,Dialog,Toast都新建了一个PhoneWindow,但是 WindowManagerImpl 作为 WindowManager 的实现类是全局唯一的
图片相关
Bitmap 如何压缩
- 利用采样压缩,即 BitmapFactory.Options 的 inSampleSize 参数,先利用 inJustDecodeBounds 参数取原始宽高,再计算采样率
Kotlin相关
Kotlin异常
Kotlin接口为什么能定义方法实现?怎么实现的?
let,with
源码相关
Retrofit 源码
- 从 retrofit.create 方法开始
- 进行方法基本校验
- 利用动态代理,在调用 接口 方法时,转移到动态代理代码内部:
- 先读取方法注解,明确访问参数,例如 GET,HEADER 等信息
- 工厂模式,从工厂获取 addCallAdapterFactory addConverterFactory
- 构造一个 CallAdapted
- 接着调用 CallAdapted 的 invoke 方法,也就是 CallAdapted 的 adapt 方法
- 在默认的 CallAdapter 也就是 DefaultCallAdapterFactory 创建的,是直接返回了 ExecutorCallbackCall 对象
- 调用 Call.enqueue,也就是调用 ExecutorCallbackCall 的 enqueue,他会调用 okHttp 的 enqueue,并将 onResponse 利用 handler 转移回主线程
OkHttp 源码
拦截器顺序
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
拦截器功能
- client.interceptors
- RetryAndFollowUpInterceptor
重定向,失败重试
- BridgeInterceptor
添加各种header的拦截器,比如Content-Type等
- CacheInterceptor
决定是否从缓存里取数据,或把response缓存住
- ConnectInterceptor
建立连接,连接池复用等,HttpCodec
- client.networkInterceptors
- CallServerInterceptor
通过 exchange 发请求收数据(Socket)
其他问题
MVC,MVP,MVVM
APK文件包含哪些东西
- classes.dex
- resources.arsc
记录 R 文件中的 id 和资源的对应关系
- res
- AndroidManifest.xml
- META-INF
签名信息
APK构建流程
APK 瘦身
- 代码:
- ProGuard 混淆可以 压缩代码体积(去掉无用代码),优化字节码,混淆代码(缩短代码命名等)
- D8和R8 android默认用 d8 来编译打包 dex 文件,而 r8 可以更好地对 kotlin 优化
- Redex
- 资源:
- 冗余资源:
- lint 检测无用代码
- 图片可以利用矢量图,webp等格式
- 冗余资源:
- So库:
- 只保留 armeabi
插件化
用处
- 解决65535
- 动态部署
- 热修复
概念
odex: optimized dex,即被优化的dex文件,手机系统为我们优化了dex
oat:Optimized Android file Type,直接转化为机器码,略过编译解释的过程
基本原理
利用 ClassLoader 加载插件的 apk(该 apk 应该拷贝到内存中以供加载),然后利用 classLoader.loadClass 加载类去调用
热更新
概念
实现方式之一
利用 ClassLoader 机制,把我们新的 dex 或 apk 放在旧的代码之前加载(必须先杀死程序再启动,因为有缓存会影响)
具体代码:
ClassLoader 在构造方法中,会根据传入的字符串分割出待加载的文件(dex或apk),然后把它们放入 DexPathList 的 elements[] 中
之后在 findClass 方法中,会遍历 elements[],找到第一个能加载当前类的 elements 并返回 class 实例,所以我们的目标就是把新的 dex 或 apk 放在 elements[] 的前面,同时 elements[] 原有的数据要依次往后放
如此这般,再 kill 掉程序并加载热更新后,就 ok 了