Android 基础

Activity 生命周期

Android Activity生命周期
onCreate,onStart,onResume,onPause,onStop,onDestroy,onRestart
注意,旧 Activity 先 onPause,新 Activity 才会 onCreate
异常情况下(旋转屏幕或低内存被杀),Activity 被回收,系统会调用 onSaveInstanceState,时机在 onStop 之前,重新创建后,会调用 onRestoreInstanceState(onStart 之后)
如果配置了 configChanges, 则会调用 onConfigurationChanged

Activity 启动模式

  1. standrard
  2. singleTop
  3. singleTask — 找寻 TaskAffinity 指定的任务栈
  4. singleInstance—具有singleTask的特性,另外该类型 Activity 会单独位于一个特殊的任务栈

复用时会调用 onNewIntent

Android 创建多进程

清单文件声明 process=”:remote”

Service使用方式,绑定和start

  1. 多次startService

    1. D/lpy: onCreate
    2. D/lpy: onStartCommand
    3. D/lpy: onStartCommand
    4. D/lpy: onStartCommand
    5. D/lpy: onStartCommand
  2. 多次 bindService

    onBind null onServiceConnected // 只调用一次

  3. 先 start 再 bind,然后 stop 再 unbind

    onCreate onStartCommand onBind null onServiceConnected onUnbind // 这是 unbind 时的输出,调用 stop 没有任何输出 onDestroy

  4. 先 bind 再 start,然后 stop 再 unbind

    onCreate onBind null onServiceConnected onStartCommand onUnbind // unbind 输出 onDestroy

  5. 先 start 再 bind,然后 unbind 再 stop

    onCreate onStartCommand onBind null onServiceConnected onUnbind // unbind onDestroy // stop

  6. 先 bind 再 start,然后 unbind 再 stop

    onCreate onBind null onServiceConnected onStartCommand onUnbind // unbind onDestroy // stop

Android 的 IPC 方式

  1. AIDL
  2. Bundle
  3. 文件
  4. Messenger
  5. ContentProvider
  6. 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的顺序:

  1. public void dispatchMessage(@NonNull Message msg) {
  2. if (msg.callback != null) {
  3. handleCallback(msg);
  4. } else {
  5. if (mCallback != null) {
  6. if (mCallback.handleMessage(msg)) {
  7. return;
  8. }
  9. }
  10. handleMessage(msg);
  11. }
  12. }

IdleHandler 用法原理?

  1. Looper.myQueue().addIdleHandler(new IdleHandler() {
  2. @Override
  3. public boolean queueIdle() {
  4. //你要处理的事情
  5. return false;
  6. }
  7. });

MessageQueue 中每次分发完 msg,都会判断 idleHandler[] 是否有值,有的话,就会调用

Looper怎么退出?

可以调用 quit 或者 quitSafely 来结束

handler内存泄漏的原因?如何避免?

handler 内部消息没弄完,内部类持有外部类引用导致泄漏

HandlerThread

HandlerThread 继承自 Thread,内部 run 方法封装了一个 handler

IntentService

IntentService 继承自 Service,内部封装了一个 handler 和 HandlerThread,也就是说 IntentService 内部有一个线程在不断地执行发送过来的消息

View相关

自定义View

image.png

Android 动画分类

  1. View 动画 — res/anim/xxx.xml

    1. TranslateAnimation —
    2. ScaleAnimation —
    3. RotateAnimation —
    4. AlphaAnimation —
    5. AnimationSet —

      使用方法: Animation anim = AnimationUtils.loadAnimation(); mBtn.startAnimation(anim);

  2. 帧动画 — res/drawable/xxx.xml

    1. AnimationDrawable —

      使用方法: mBtn.setBackgroudResource(R.drawable.xxx); AnimationDrawable drawble = (AnimationDrawable) mBtn.getbackground(); drawable.start();

  3. 属性动画 — res/animator/xxx.xml

    1. ValueAnimator
    2. ObjectAnimator
    3. AnimatorSet

      插值器 Interpolator

  1. 根据系统给出的 input,计算相应的执行百分比(0 ~ 1
  2. valueAnimator.setInterpolator { input: Float ->
  3. input
  4. }

估值器 TypeEvaluator

  1. 插值器算出来的百分比作为估值器的参数
  2. valueAnimator.setEvaluator(object : TypeEvaluator<Float> {
  3. override fun evaluate(fraction: Float, startValue: Float, endValue: Float): Float {
  4. return (startValue + fraction * (endValue - startValue))
  5. }
  6. })

监听 AnimatorUpdateListener / AnimatorListener()

  1. Activity 切换动画(View动画的一种形式)

    1. 在 startActivity 或者 finish 之后,调用 overridePendingTransition(anim1, anim2);

      事件分发流程?手指按下btn然后不抬手移出btn,此时事件如何传递?源码是如何判断的?

      mFirstTouchTarget(单链表)是成功处理事件的子View

      1. @Override
      2. public boolean dispatchTouchEvent(MotionEvent ev) {
      3. if (actionMasked == MotionEvent.ACTION_DOWN) {
      4. cancelAndClearTouchTargets(ev); // DOWN事件清除所有标记
      5. resetTouchState();
      6. }
      7. if (actionMasked == MotionEvent.ACTION_DOWN
      8. || mFirstTouchTarget != null) {
      9. // 如果 DOWN 事件 或者 子View 能处理某事件 进入
      10. final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      11. if (!disallowIntercept) {
      12. intercepted = onInterceptTouchEvent(ev);
      13. }
      14. }
      15. if (!canceled && !intercepted) { // 不取消,不拦截
      16. if (actionMasked == MotionEvent.ACTION_DOWN) {
      17. for (int i = childrenCount - 1; i >= 0; i--) {
      18. if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//分发给子View
      19. //如果子View能处理,mFirstTouchTarget 就被赋值
      20. newTouchTarget = addTouchTarget(child, idBitsToAssign);
      21. }
      22. }
      23. }
      24. }
      25. if (mFirstTouchTarget == null) {
      26. // 没有子View可以处理,那么调用super.dispatchTouchEvent(),即会调用自己的 onTouchEvent
      27. handled = dispatchTransformedTouchEvent(ev, canceled, null,
      28. TouchTarget.ALL_POINTER_IDS);
      29. } else {
      30. // 有子View可以处理,那么分发给子View
      31. if (dispatchTransformedTouchEvent(ev, cancelChild,
      32. target.child, target.pointerIdBits)) {
      33. handled = true;
      34. }
      35. }
      36. if (canceled
      37. || actionMasked == MotionEvent.ACTION_UP
      38. || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
      39. // 如果是取消或者抬起事件,直接重置变量
      40. resetTouchState();
      41. }
      42. }


      滑动冲突如何处理?

  2. 外部拦截

    1. 重写 onIntercepTouchEvent
    2. DOWN 不拦截
    3. MOVE 在需要的时候拦截
    4. UP 不拦截
  3. 内部拦截
    1. 父 ViewGroup 拦截非 DOWN 事件
    2. 子 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 如何压缩

  1. 利用采样压缩,即 BitmapFactory.Options 的 inSampleSize 参数,先利用 inJustDecodeBounds 参数取原始宽高,再计算采样率

    Kotlin相关

    Kotlin异常
    Kotlin接口为什么能定义方法实现?怎么实现的?
    let,with

源码相关

Retrofit 源码

  1. 从 retrofit.create 方法开始
    1. 进行方法基本校验
    2. 利用动态代理,在调用 接口 方法时,转移到动态代理代码内部:
      1. 先读取方法注解,明确访问参数,例如 GET,HEADER 等信息
      2. 工厂模式,从工厂获取 addCallAdapterFactory addConverterFactory
      3. 构造一个 CallAdapted
      4. 接着调用 CallAdapted 的 invoke 方法,也就是 CallAdapted 的 adapt 方法
      5. 在默认的 CallAdapter 也就是 DefaultCallAdapterFactory 创建的,是直接返回了 ExecutorCallbackCall 对象
  2. 调用 Call.enqueue,也就是调用 ExecutorCallbackCall 的 enqueue,他会调用 okHttp 的 enqueue,并将 onResponse 利用 handler 转移回主线程

OkHttp 源码

拦截器顺序

  1. val interceptors = mutableListOf<Interceptor>()
  2. interceptors += client.interceptors
  3. interceptors += RetryAndFollowUpInterceptor(client)
  4. interceptors += BridgeInterceptor(client.cookieJar)
  5. interceptors += CacheInterceptor(client.cache)
  6. interceptors += ConnectInterceptor
  7. if (!forWebSocket) {
  8. interceptors += client.networkInterceptors
  9. }
  10. interceptors += CallServerInterceptor(forWebSocket)

拦截器功能

  1. client.interceptors
  2. RetryAndFollowUpInterceptor

重定向,失败重试

  1. BridgeInterceptor

添加各种header的拦截器,比如Content-Type

  1. CacheInterceptor

决定是否从缓存里取数据,或把response缓存住

  1. ConnectInterceptor

建立连接,连接池复用等,HttpCodec

  1. client.networkInterceptors
  2. CallServerInterceptor

通过 exchange 发请求收数据(Socket)

其他问题

MVC,MVP,MVVM

截屏2021-01-22 下午1.11.07.png

APK文件包含哪些东西

  1. classes.dex
  2. resources.arsc

记录 R 文件中的 id 和资源的对应关系

  1. res
  2. AndroidManifest.xml
  3. META-INF

签名信息

APK构建流程

截屏2021-01-21 下午4.05.23.png

APK 瘦身

  1. 代码:
    1. ProGuard 混淆可以 压缩代码体积(去掉无用代码),优化字节码混淆代码(缩短代码命名等)
    2. D8和R8 android默认用 d8 来编译打包 dex 文件,而 r8 可以更好地对 kotlin 优化
    3. Redex
  2. 资源:
    1. 冗余资源:
      1. lint 检测无用代码
      2. 图片可以利用矢量图,webp等格式
  3. So库:
    1. 只保留 armeabi

插件化

用处

  1. 解决65535
  2. 动态部署
  3. 热修复

    概念

    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 了