1.序列化性能对比:

Parcelable与Serializable性能对比:
(1). 在内存的使用中,前者在性能方面要强于后者
(2). 后者在序列化操作的时候会产生大量的临时变量,(原因是使用了反射机制)从而导致GC的频繁调用,因此在性能上会稍微逊色
(3). Parcelable是以Ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable,既然是内存方面比价有优势,那么自然就要优先选择.
(4). 在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上.
但是:虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)

2.onSaveInstanceState()调用时机:
Q:AMS通过binder通信,告诉Application需要pauseActivity,Application通过Handler将任务转到ActivityThread的工作线程,然后ActivityThread通过Instrumentation先执行Activity的performSaveInstanceState()方法,然后执行onSaveInstanceState()方法,然后再执行Activity的performPause()方法,再执行onPause()方法。

3.dexclassloader作用:

Java中的类加载器:
BootstrapClassLoader -> ExtClassLoader -> AppClassLoader

- BootstrapClassLoader:
该加载器是C、C++实现的,加载系统属性“sun.boot.class.path”配置下的类文件。一般加载jdk/Contents/Home/jre/lib下的jar包和class文件。

- ExtClassLoader:
加载系统属性“java.ext.dirs”配置下的类文件。一般是加载jdk/Contents/Home/jre/lib/ext下的类文件。

- AppClassLoader:
加载系统属性“java.class.path”配置下的类文件,也就是CLASS_PATH配置的路径。一般加载自己写的类和第三方jar包中的类。

向上委托:loadClass()方法会先调用父类加载器的loadClass()方法,没有父类使用BootstrapClassLoader加载;
向下分派:父类没有找到class,就返回让子类加载器去找class。

比较重要的三个方法:
- loadClass():如果需要破坏双亲委派模型,就要重写此方法。
- findClass():自定义class字节码的获取方式,可以是磁盘、网络等。
- defineClass():一般在findClass中使用,将获取到的class字节码转换成Class对象。

Android中的类加载器:
Android虚拟机里无法直接运行.class文件,Android会将所有的.class文件转换成一个.dex文件,并且将加载.dex文件的实现封装在BaseDexClassLoader中。

- PathClassLoader:
用来加载系统apk和被安装到手机中的apk中的dex文件。当一个app安装到手机后,apk里的class.dex中的所有class都是由这个加载器加载。
- DexClassLoader:
对比PathClassLoader只能加载已经安装应用的dex或apk文件,此加载器没有这个限制,可以从磁盘中获取dex或apk文件,然后加载class,这是插件化和热修复的基础。
第一个参数dexPath:包含class.dex的apk、jar文件路径,多个路径用文件分隔符(默认是“:”)分隔;
第二个参数optimizedDirectory:用来缓存从apk、jar中提取出来的优化后的dex文件路径,此路径不可以为空,且需要是应用的私有可读写的路径。

4.SharedPreference原理:

getSharedPreferences(String name, int mode),每个name都对应一个File,每个应用都有一个File-SharedPreferences映射关系的ArrayMap,通过ContextImpl拿到的是SharedPreferences的实现类SharedPreferencesImpl,在SharedPreferencesImpl类初始化时,会创建子线程读取文件中保存的key—value内容,从File中加载key-value的内容到Map中,这个子线程执行会获得锁,只有等到这个子线程完成,SharedPreferencesImpl才能执行其他方法,比如:getInt()会等到读取内容的子线程完成后,直接从Map中获取value。
如果需要修改或保存数据,需要创建Editor子类EditorImpl对象,然后调用如putString()方法,将数据先储存在Editor的Map中,然后调用commit()或者apply()方法,这个方法会先将Map内容更新到SharedPreferencesImpl中的Map中,然后把“将内容保存到File”的任务放到子线程的消息队列中执行。

需要注意的:
(1).commit()方法会将写入到磁盘的任务放在子线程的消息队列,那么如果再消息队列还没有执行完成的时候应用退出了,将会直接调用MessageQueue.quit()方法,将后续没有执行的任务全部移除掉,就会导致数据的丢失。
apply()方法与commit()方法不同的是,apply()会将写入磁盘的任务放在结束链表里面,那么在Activity基类的onPause()中,在BroadcastReceiver的onReceive之后,在Service命令处理之后调用QueueWork.waitToFinish()方法,先将剩余的写入磁盘的任务执行完,因此apply()这种方式可以保证在应用没有异常退出的情况下,写入磁盘的数据不会丢失,但是缺点就是会阻塞UI线程!!
(2).一般getSharedPreferences在应用启动后在子线程调用,因为将File中的数据写入到内存需要时间,因此在使用getInt()的时候就不会因为初始化时的阻塞而耗时了;
(3).由于getSharedPreferences调用之后,File中的内容都加载了内存中,放在SharedPreferencesImpl的cache中,这个cache的数据会一直保存到应用结束,因此不建议SharedPreference来保存大数据,这样会导致大内存持续占用;
(4).不要使用sharedPreferences.editor().putxxx(),而是先获取Editor对象,然后再调putxxx(),因为.editor()方法会创建Editor对象,频繁调用会产生大量的GC引起内存抖动。

MMKV:优点:

(1).mmap 内存映射文件,实时写入数据,不必担心crach导致数据丢失;

(2).数据序列化采用protobuf 协议,读写性能很好;

(3).基于protobuf 协议,可以增量更新,优化写入速度。

5.Activity正常和异常生命周期:

Activity的异常生命周期:
导致异常生命周期的情况:
(1).相关的系统配置发生改变导致Activity被杀死并重新创建(一般指横竖屏切换),当然改变configChanges的属性也可以让屏幕旋转的时候不重建Activity;
(2).内存不足导致低优先级的Activity被杀死。

生命周期:
(1)异常杀死:onSaveInstanceState()保存Activity状态 -> onDestroy();  这里不能确定onSaveInstanceState()和onPause()方法的先后顺序。
(2)重建:onCreate() -> onStart() -> onRestoreInstanceState(): onCreate()中savedInstanceState参数使用时需要进行判空处理,因为Activity正常启动时其Bundle参数是为null的,而onRestoreInstanceState()一旦被调用,其参数必定有值;onRestoreInstanceState()方法里面可以使用Bundle存储自己需要的数据。

Activity的状态保存在ActivityClientRecord.Bundle state中。

6.App context 和 Activity context区别:

Context个数 = Activity + Service + 进程数(因为每一个进程都会有一个Application示例)

(1)继承不同:
Appliction 、Service继承自ContextWrapper--->再继承Context
Acitiivity 继承自ContextThemeWrapper--->再继承ContextWrapper--->Context
(2)生命周期不同:
Application的Context的生命周期是应用的生命周期
Activity的Context的生命周期跟Activity的生命周期一致
(3)使用限制:
Application的Context在创建的时候没有传入activityToken(IBinder)
Activity的Context在创建的时候传入activityToken
startActivity以及Window相关操作是需要activityToken的,因此Application的Context不能用在如show dialog、startactivity、layout等地方。具体可以看12点。

7.Android GC算法:

GC算法:
(1)标记-清除:
将无用对象标记,并清除。
缺点:效率低,产生大量不连续的内存碎片,导致下次申请内存时有可能导致找不到合适的内存空间。
(2)复制算法:
将内存容量分成两个相同大小的区域,只使用其中一个区域。回收时将存活的对象复制到另一个区域,然后把之前的区域全部清除。
缺点:虽然效率提升了,但是内存使用的大小减小了一半。
(3)标记-整理:
将无用对象标记,将存活的对象移到一端,然后直接清除端边界以外的内存。
(4)分代收集:
按照对象的生命周期分为新生代和老年代,新生代的存活对象少,采用复制算法;老年代的存活对象多,采用标记-清除 或 标记-整理算法。

如何判定对象不存活:
采用可达性分析法,根据GC Roots遍历引用链,没有在引用链上的对象就是无用对象。GC Roots主要是在全局性的引用(如常量、类静态属性)与执行上下文(如栈帧中的本地变量表)中。

8.hashcode和equal:

hashcode:
例如有hash表,槽位为8,那么存入的对象通过计算hash值对8取余获得hashcode(0-7),然后将对象存储到hashcode对应的槽位。
hashcode的算法很多,但是不能保证不同对象计算出来的hashcode不一样,这样就会存在hash冲突。

我们使用的HashMap原理就使用的了hashcode,这样的好处在于,我们从HashMap中查找元素比较快。HashMap在遇到hash冲突时,解决的办法是拉链法,即在同一个槽位存储一个链表。
当我们查找某个元素时,通过计算hashcode找到槽位,如果槽位只有一个元素,那么就直接返回,时间复杂度是O(1)跟数组一样,如果槽位不止一个元素,也就是一个链表,那么就遍历链表,通过equals方法来判断key值是否相等,再返回对应的value值。
所以HashMap结合hashcode和equals来设计的,那么就能得出结论:
(1)hashcode相等,不代表equals为真;
(2)equals为真,那么hashcode就一定相等(在不重写hash方法的前提)
(3)如果重写equals方法,那么建议也重写hashcode方法,否则可能出现equals为真,hashcode不相等的情况。

注意:java1.7及以前的HashMap使用的是数组+链表,java1.8及之后使用的是数组+(链表或红黑树):
Q:为什么是链表或红黑树呢?
A:HashMap使用链表时,插入是非常快的,因为新元素是插入到链表的头部,但是链表的查询和删除就比较慢了,需要从头开始遍历然后equals;而红黑树的特点是查找和删除比较快,当然插入其实也快,但是没有像链表直接插入到头部那样快。因此在java1.8的时候,HashMap的设计是当一个槽位的元素个数小于8时使用链表,否则使用红黑树。HashMap的红黑树是使用hash值来进行大小的判断来进行查询、插入、删除的,注意在这里不是hashcode(槽位索引),否则同一个槽位的所有元素所对应的大小都是一样无法使用红黑树。但是如果插入元素的hash值跟红黑树中有一个node的hash值一样那该怎么插入呢,这里其实hash值一样也没关系,一样就插入左子树或者右子树,对以后的插入没有影响,获取的时候会通过比较key,相同就返回,如果hash值一样,就会先遍历右子树,然后遍历左子树,直到key值相同为止。System.identityHashCode,这个方法是java根据对象在内存中的地址算出来的一个数值,不同的地址算出来的结果有可能一样,在Java对象没有重写hash()方法时,这个hash()方法和System.identityHashCode()的值一样,但是如果重写hash()的方法后依然想使用以前的hash()计算出的hash值就可以使用System.identityHashCode()方法,因为是散列,因此都有可能重复(尽管使用的是Java对象的内存地址)。源码在openjdk:Java_java_lang_System_identityHashCode

9.如何终止一个线程的运行:

终止一个线程的方法:
(1)使用标志。
(2)stop():
该方法已经废弃:
- 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
- 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
(3)interrupt():
调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程,可以在循环里查看这个标志来决定是否退出。
这么一看跟第一种方式很像,但是如果线程在sleep() 或者 wait()时,第一种方式就没有效果了,可以看到sleep()和wait()方法都会向上级调用抛出InterruptedException异常,其实就是在线程调用interrupt()方法时,会导致线程在sleep()和wait()中被唤醒并抛出异常。因此interrupt()是比较推荐的一种方式。

10.为什么子线程报异常,Android应用退出:

ThreadGroup:
每个线程Thread对象都有一个ThreadGroup,在创建Thread对象的时候,如果没有指定ThreadGroup那么,这个对象的ThreadGroup就是当前创建线程的ThreadGroup。

ThreadGroup:
Thread threads[]:记录线程组里面的所有Thread;
ThreadGroup groups[]:记录线程组里面所有的子线程组。
因此ThreadGroup可以包含Thread和ThreadGroup。

try{}catch{}:
- 类会跟随一张 异常表(exception table),每一个try catch都会在这个表里添加行记录,每一个记录都有4个信息(try catch的开始地址,结束地址,异常的处理起始位,异常类名称);
- 当代码在运行时抛出了异常时,首先拿着抛出位置到异常表中查找是否可以被catch(例如看位置是不是处于任何一栏中的开始和结束位置之间),如果可以则跑到异常处理的起始位置开始处理,如果没有找到则原地return,并且copy异常的引用给父调用方,接着看父调用的异常表。

出现异常后,会去这个方法的异常表中查找是否有符合的异常,如果有就执行catch中的方法,如果没有就向上一级方法调用查找,一直到最后都没有找到对应的异常时,native层就会抛出异常,这里的操作实际上是native的Thread对象设置异常的方法SetException(),将异常保存在上下文中。线程出现异常会导致线程退出,大致的调用流程:

因此可以得出:在当前线程没有设置UncaughtExceptionHandler,且当前线程所属的ThreadGroup以及其祖辈ThreadGroup都没有设置UncaughtExceptionHandler,且没有通过Thread.setDefaultUncaughtExceptionHandler()方法设置自定义的UncaughtExceptionHandler的情况下,子线程出现异常后,没有捕获,就会调用默认的异常处理方法,会调用Process.killProcess(Process.myPid()); System.exit(10);杀死进程,导致应用异常退出。

按照上面的方法,如果主线程有未捕获的异常,那么主线程在抛异常的地方就会返回,也就相当于结束了主线程,跟挂死差不多,那么有什么办法做到主线程也不会挂死呢?

因为安卓系统的工作都是基于消息机制的,也就是Looper.loop()在不断的接收并处理任务,那么如果能够将Looper.loop()进行try...catch,就可以避免程序会异常退出,如果上图所示,在一个死循环里面执行Looper.loop(),一旦Looper.loop()抛出异常,就重新启动Looper.loop()。

11.Thread sleep 和 interrupt原理:

查看第9点。

sleep和wait的区别:
(1)sleep是线程中的方法,但是wait是Object中的方法。
(2)sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
(3)sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字(使用层面,但是从源码看都是使用了synchronized)。
(4)sleep不需要被唤醒(休眠之后退出阻塞),但是wait需要(不指定时间的话需要被别人中断),但是sleep和wait都可以在线程中断时被唤醒并抛出中断异常。

sleep和wait底层都是通过管程对象(Monitor)来实现的,Monitor.wait(Thread, Obj, ms, ns, interruptShouldThrow, ThreadState)。
其中:
- Thread就是当前调用的线程,注意这个是C++对象。
- Obj:sleep方法会创建一个Java对象Lock,这个Obj就是这个Lock;而wait是Object对象的方法,传递是它自身。
- interruptShouldThrow:是一个bool值,表示是否希望在Thread.interrupt调用中断时报出异常并返回,这个值在sleep和wait底层实现一直是true。
- ThreadState:sleep()方法对应ThreadState.kSleeping,wait()方法对应ThreadState.kWaiting,wait(long millis)方法对应ThreadState.kTimedWaiting。因此在Monitor.wait()方法中实现不同逻辑。

interrupt():底层是通过C++层的Thread调用pthread_cond_signal实现,pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行。

sleep和yield的区别:
(1)sleep方法给其他线程运行机会时不考虑线程的优先级,因此会给低线程优先级运行的机会,而yield方法只会给相同优先级或者更高优先级线程运行的机会
(2)线程执行sleep()方法后转入阻塞状态,所以,执行sleep()方法的线程在指定的时间内不会被执行,而yield()方法只是使当前线程重新回到可执行状态,所以执行yield()方法的线程可能在进入可执行状态后马上又被执行
(3)sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常
(4)sleep()方法比yield()方法(跟操作系统相关)有更好的可移植性

notify和notifyAll的区别:
(1)如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
(2)当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
(3)优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

notify和notifyAll底层都是通过管程对象Monitor来实现的:
Monitor对象有一个Thread的链表wait_set_作为等待池。

锁机制:C++层互斥量:
(1)pthread_mutex_init():
功能:初始化一个互斥锁
(2)pthread_mutex_destroy():
功能:销毁一个互斥锁
(3)pthread_mutex_lock():
功能:加锁,阻塞的去获取锁,当这个函数返回时,表示已经获得锁
(4)pthread_mutex_trylock():
功能:尝试加锁,非阻塞的去获取锁,是否获取锁都会立即返回
(5)pthread_mutex_unlock():
功能:解锁
上面所有的函数,参数都是pthread_mutex_t的指针,pthread_mutex_t是一个结构体,里面有两个成员 long、char数组

12.Activity  window  view 关系;

ActivityThread.performLaunchActivity():
    - Activity.attach():创建PhoneWindow作为Activity的成员变量;通过Context.getSystemService()获取WindowManagerImpl对象赋值给成员WindowManager;
    - attach方法在onCreate之前;
    - 在onCreate()方法里,调用setContentView()方法,调用PhoneWindow的setContentView()方法,再调用installDecor()来创建DecorView,不过此时只是创建DecorView,还没有将其与Window建立联系。

ActivityThread.handleResumeActivity():
    - 从Window中获取DecorView并赋值给Activity的成员;
    - Activity.makeVisible():通过ViewManager(实例为WindowManagerImpl)的addView方法,创建ViewRootImpl用来管理这个Window下所有的View,将DecorView加入到View的List集合中;
    - 通过ViewRootImpl的setView()方法将DecorView作为ViewRootImpl的mView成员并调用requestLayout()方法触发View的绘制,这里只有加入DecorView时才会执行这些操作;
    - 将DecorView设置为可见状态。

每个Activity会有一个WindowManager对象,这个mWindowManager就是和WindowManagerService(WMS)进行通信,也是WMS识别View具体属于那个Activity的关键,创建时传入IBinder 类型的mToken。
mWindow.setWindowManager(...,mToken, ...,...)
这个Activity的mToken,这个mToken是一个IBinder,WMS就是通过这个IBinder来管理Activity里的View。

现在来看在Activity通过 getWindowManager().addView(mView,wl)和 addContentView(mView,wmParams)的区别。第一种情况会调用到WindowManagerGlobal.addView,这时会创建一个新的ViewRootImpl,和原来的DecoView不在一条View链上,所以它们之间的任何一个调用requestLayout()不会影响到另一个。而addContentView(mView,wmParams)是直接将mView添加到DecoView中,会使ViewRootImpl链下的所以View重绘。

Dialog不能通过非Activity的Context,如Application 和 Service,这是因为Dialog通过传入的Context来得到context里的mWindowManager(也就是WindowManagerImpl)与mToken,这是为了表明Dialog所属的Activity,在Window.addView时,需要这个mToken(IBinder对象),而Application 和 Service传入的情况下Token是null。

ViewRootImpl是实际管理Window中所以View的类,每个Activity中ViewRootImpl数量取决于调用mWindowManager.addView的调用次数。

Activity提供和WMS通信的Token(IBinder对象),DecoView结合ViewRootImpl来管理同一View链(有相同的ParentView的View,ViewRootImpl也就是ParentView)的所以View的事件,绘制等。那Window的意义在哪?虽然Window也就是PhoneWindow没有具体做什么,但Window把Activity从View的一些创建,管理以及和ViewRootImpl的交互中脱离出来,让Activity与View尽量解耦。

Activity提供与AMS通信的Token(IBinder对象),创建Window为View提供显示的地方,而具体的View管理任务由ViewRootImpl来完成。

ViewParent:
是一个接口,View没有实现,ViewGroup和ViewRootImpl实现了该接口;

invalidate():改变当前View的mPrivateFlags标识参数,调用View的damageInParent()方法,这个方法将层层调用父View的onDescendantInvalidated()方法(这个方法是ViewParent接口的方法)来改变mPrivateFlags参数,标识是否需要重新绘制(GPU绘制则不会直接将父View直接设置为dirty状态,如果是CPU绘制就会直接设置为dirty状态),直到调用到ViewRootImpl的onDescendantInvalidated()方法。
ViewRootImpl的onDescendantInvalidated()方法会调用ViewRootImpl的invalidate()方法。

requestLayout():
(1)如果当前正在layout:
- 当这个layout还在执行,就将这个View加入到ViewRootImpl的mLayoutRequesters(List)中,等待Layout;
- 当这个layout执行完成,就执行第(2)步;
(2)如果当前没有在layout:
就将mPrivateFlags参数设置成需要layout状态,层层调用ViewParent的requestLayout()方法,直到调用到ViewRootImpl的requestLayout()方法。

ViewRootImpl的invalidate()和requestLayout()方法都会去执行View的绘制流程。

View的绘制流程:
(1)ViewRootImpl.scheduleTraversals():
    - Handler插入同步屏障
    - Choreographer.postCallback发送一个执行doTraversal()方法的消息;
        - 将这个消息放到CallbackQueue[]里面,CallbackQueue是四个不同类型(Input、Animation、Traversal、Commit)消息的队列的数组,等待VSYNC信号的到来再执行。
        - scheduleFrameLocked()通过FrameDisplayEventReceiver类的scheduleVsync()方法使用jni通知来安排一个垂直同步信号(VSync);
(2)FrameDisplayEventReceiver.onVsync():这个方法是native层通过jni调用的:
    - run()方法中调用doFrame方法:
        - 这里会依次顺序的执行Input、Animation、Traversal、Commit消息(与dumpsys gfxinfo得到的数据顺序一样)
            - 如果只看绘制流程,那么上面的Traversal消息就是执行doTraversal()方法:
                - 移除同步屏障
                - 调用performTraversals()方法(这个方法就是执行整个View的绘制的起点)
(3)performTraversals():从上面可以看出performTraversals()方法的执行是通过垂直同步信号(VSync信号)来触发的,当然是否使用垂直同步消息的策略是通过一个参数USE_VSYNC来控制的:

会依次执行measure、layout、draw过程,具体不再详述。

13.Handler如何实现延时;

在调用MessageQueue.enqueue()方法时,如果Msg需要延时,那么msg.when=当前时间+延时时间,插入msg的时候会遍历Message链表,如果这个msg.when比较大,则往后排,因此Message链表是根据when来排序的。

14.按钮太小,如何扩大按钮事件响应范围  TouchDelegate

15.Fragment生命周期:

Fragment的生命周期由Activity的 FragmentController.FragmentHostCallback.FragmentManager管控,FragmentManager会将遍历已经添加进来的所有Fragment,并执行它们的生命周期。

16.CountDownLatch与CyclicBarrier:

CountDownLatch:
- CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
- 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
使用的方式:
CountDownLatch countDownLatch = new CountDownLatch(n);//其中n为需要等待n个线程完成
countDownLatch.countDown();//这个方法一般在需要先行执行的线程Run方法的最后调用,用于将计数器减一
countDownLatch.await();//这个方法一般是放在等待的线程中,表示等待前面n个线程完成之后再开始执行后续的代码。

CyclicBarrier:(一个可循环利用的屏障)
- 让所有线程都”完成任务“后,所有线程才能继续往下执行,否则就要等待所有线程完成。
使用方式:
CyclicBarrier cyclicBarrier = new CyclicBarrier(n);//这是第一个构造方法,其中n为参与任务的线程数量;
CyclicBarrier cyclicBarrier = new CyclicBarrier(n, Runnable());//这是第二个构造方法,其中n为参与任务的线程数量,Runnable是最后一个线程完成任务后需要执行的代码;
cyclicBarrier.await();//这个方法一般在参数的线程中调用,表示该线程已经”完成任务“(即代码执行到这里了),然后需要等待其他线程执行到await()方法,一直到有n个线程已经执行到await(),然后所有线程才开始执行接下来的代码。

17.invalidate和requestLayout:

具体看12点

18.MeasureSpec、Measure时机:

具体看12点

19.泛型的边际

20.懒加载机制优化UI性能

21.自定义View里面的知识--属性动画

22.ThreadLocal的使用和原理:
ThreadLocal有一个内部类,ThreadLocalMap,同一个ThreadLocal对象,在一个线程中只能存放一个value,多次存放对覆盖,因此需要存储多个value,需要创建ThreadLocal对象。
ThreadLocal实际操作的是当前线程Thread的成员变量ThreadLocal.ThreadLocalMap,通过ThreadLocal.get(Thread)可以获取当前线程的ThreadLocal.ThreadLocalMap对象,然后将当前的ThreadLocal实例对象作为key,要保存的实例对象作为value,保存在Thread的成员ThreadLocal.ThreadLocalMap中。

23.Bitmap位图算法:
一个字节有8位,每个bit(位)代码一个自然数,比如:
byte[0]: 7 6 5 4 3 2 1 0
byte[1]: 15 14 13 12 11 10 9 8
...
byte[n]: 8n-1 8n-2 ... 8n-8
可以利用这种算法来去重、排序、压缩等。
有占用空间小的优点,比如:要存储0~1024个不重复int,正常需要空间的大小为1024 * 32个字节,但是使用这种算法后只需要1024 / 8个字节。

24.Handler的原理:

图解:

  • 红色虚线关系:Java层和Native层的MessageQueue通过JNI建立关联,彼此之间能相互调用,搞明白这个互调关系,也就搞明白了Java如何调用C++代码,C++代码又是如何调用Java代码。
  • 蓝色虚线关系:Handler/Looper/Message这三大类Java层与Native层并没有任何的真正关联,只是分别在Java层和Native层的handler消息模型中具有相似的功能。都是彼此独立的,各自实现相应的逻辑。
  • WeakMessageHandler继承于MessageHandler类,NativeMessageQueue继承于MessageQueue类

另外,消息处理流程是先处理Native Message,再处理Native Request,最后处理Java Message。理解了该流程,也就明白有时上层消息很少,但响应时间却较长的真正原因。

当Java层没有消息时,调用nativePollOnce(ptr, -1);会进行阻塞,当MeasureQueue.enqueue()方法执行时,如果当前是阻塞状态就会调用nativeWake(mPtr);唤醒阻塞。

MessageQueue.addOnFileDescriptorEventListener(@NonNull FileDescriptor fd, @OnFileDescriptorEventListener.Events int events, @NonNull OnFileDescriptorEventListener listener)方法可以向fd中的等待队列添加eventpoll,在中断处理程序中加入eventpoll的写操作,这样当fd有读写操作时,就会向eventpoll的RDList(就绪列表)中写入fd引用,这将导致eventpoll的等待队列被唤醒,将事件写入到Responses数组中,在native的Loop循环中处理这些Responses,然后回调这个listener,并将events信息返回给Java层(写入,读取,错误,悬挂),从而达到监听文件描述符的功能。

Handler如何只执行异步消息,同步消息暂时先不执行?
异步消息:
异步消息是通过一个标志位来标识的,通过调用Message.setAsynchronous()可以设置同步还是异步。

如果需要只执行异步消息,需要插入同步屏障:
通过MessageQueue.postSyncBarrier()来插入一个Message到when>当前时间的Message前面,这个Message的target为null(关键点),arg1为同步屏障的token(暂时唯一的作用是在移出同步屏障时,遍历Message时用来判断是否是同步屏障消息)。插入同步屏障不会去唤醒MessageQueue。
通过MessageQueue.removeSyncBarrier(token)来移除同步屏障,是否移除是根据target是否为null或者arg1是否为token来判断。

插入同步屏障后,在MessageQueue.next()方法里,在同步屏障消息前面的Message还是会全部执行完,当执行同步屏障消息时(判断Message.target是否为null),就会遍历获取接下来的Message,如果Message是异步消息(Message.isAsynchronous()判断)就执行此消息。这就是同步屏障的原理。

将消息插入头部:

Handler.sendMessageAtFrontOfQueue():调用该方法将消息插入到消息队列的头部,其原理是:调用的插入消息的方法还是MessageQueue.enqueue(),只是传入的when是0,那么一定会将消息插入到消息队列的第一个,也可以看作该消息的优先级最高。

25.Glide概要:

Glide,Context不属于ApplicationContext且调用的时候在主线程,那么RequestManager的生命周期是通过添加一个没有UI的Fragment来监听,而Fragment的生命周期随着Activity的生命周期的执行而执行。
如果Context属于ApplicationContext或者调用在子线程,那么RequestManager的生命周期保持与应用的一致。

Glide实现ComponentCallbacks2接口,这个接口同样也是Application实现的接口,glide初始化完成后,将glide注册到Application的ComponentCallbacks2回调中,响应onTrimMemory()方法来提示内存是否不足,这里回调内存缓存池、Bitmap缓存池、对象缓存池的trimMemory()方法。

RequestManager:该对象创建后会保存保存到Glide示例中,唯一的作用是当Application回调onTrimMemory()方法的时候,会回调Glide的onTrimMemory()方法接着会遍历Glide中RequestManager的集合,回调每一个RequestManager的onTrimMemory()方法。

GlideContext:存储一些引擎、工厂、监听、池、Register等等信息,包括Context。

Registry:用于添加或替换Glide的加载、编码、解码逻辑。

GlideBuilder:用于创建Glide实例,继承AppGlideModule,重写applyOptions()方法,就会有一个参数是GlideBuilder,可以使用GlideBuilder来设置一些内存缓存池、缓存计算器、Bitmap缓存池等,最后会将这些提供给Glide来初始化并且使用。

Bitmap复用池:inBitmap是在BitmapFactory中的内部类Options的一个变量,在Android19(4.4)之前只能复用size一模一样的Bitmap,在Android19(4.4)以后只要size比需要的大或者等于就可以复用了。
使用:
1.复用的前提就是旧的Bitmap的options.inMutable = true;//如果为true, 那么该bitmap缓存区可以被修改,否则不可被修改;
2.需要创建的Bitmap,从BitmapFactory中创建的时候,参数Options.inBitmap = 旧的Bitmap,这样就让新的Bitmap复用旧的Bitmap的内存。

Glide.with(Context).asBitmap().load(Url).into(Target):
(1)Glide.with(Context)得到的是RequestManager:这里去进行Glide的初始化(包括添加或替换加载、编解码逻辑组件,GlideContext上下文的初始化,各种缓存池、对象池、引擎等初始化,RequestManager的初始化和生命周期的监听等),并获取RequestManager;
(2).asBitmap()得到的是RequestBuilder:初始化RequestBuilder,设置RequestOptions(包含是否跳过内存缓存、磁盘缓存、错误和正在加载占位图、大小等等一些配置);
(3).load(Url)得到的依然是RequestBuilder:仅仅将Url设置到RequestBuilder,供后面使用;
(4).into(Target):Target将ImageView(当然也可以是其他的视图)和数据请求关联起来,其中setRequest和getRequest可以用来与ImageView绑定来防止图片加载错位,一般使用View.setTag和View.getTag来使ImageView和Request绑定起来。.into(Target)方法将创建Request对象,将Request对象加入到RequestManager的成员RequestTracker的集合中,用于同一管理Request的暂停和启动。Request.begin()方法将Request任务加入到工作引擎中Engine.load();

Engine.load():
(1)从内存缓存中拿资源:先从弱引用的HashMap中获取资源,如果没有则从LruCache中获取资源,从LruCache中移除并加入到弱引用的HashMap中,注意这里的LruCache保存是资源的强引用。可以理解为正在显示的资源使用弱引用的HashMap保存,没有显示的资源的强引用放在LruCache中,当空间不足会将LruCache中不常使用的资源释放掉。这里的LruCache不是Android SDK中的,而是Glide自己写的,不过原理差不多,都是使用的LinkedHashMap数据结构。
(2)从磁盘缓存中拿资源:从DiskLruCache中获取File,当从网络加载完资源后会将其保存至磁盘,根据配置保存采样/转换后的资源,还是原始资源,并将File缓存在DiskLruCache中。那么从DiskLruCache中取File的时候也会根据配置来判断是取采样/转换后的File,还是原始File。最后根据File(Model)从Registry中找到对应的ModelLoader,然后通过ModelLoader.DataFetcher.loadData()来加载资源。
(3)从网络中拿资源:最后根据Uri(Model)从Registry中找到对应的ModelLoader,然后通过ModelLoader.DataFetcher.loadData()来加载资源。
注意:可以自定义ModelLoader和ModelLoader.DataFetcher来加载从其他源中获取的数据,通过Registry将Model(也就是load()中的参数)和ModelLoader绑定在一起,一般继承AppGlideModule,重写registerComponents()方法,这个方法会有一个参数就是Registry对象,在此对数据加载模块进行注册即可。

Engine.load()从上面(1)、(2)获取到资源后,通过callback回调给Target;在(3)获取到资源后,先将资源存储在弱引用的HashMap中,然后通过callback回调给Target。

上面(1)的过程是在主线程执行的,因为直接从缓存中拿不会怎么耗时,(2)(3)则是在线程池(ThreadPoolExecutor)中执行,执行完成之后,通过Handler将资源回调方法主线程执行。

26.二叉树、搜索二叉树、AVL树、红黑树:

二叉树:
有三种遍历的方式:
(1)前序遍历:中->左->右;(2)中序遍历:左->中->右;(3)后序遍历:左->右->中.
因此可以看出中序遍历是升序遍历。

二叉搜索树:
中序遍历,如果将一组数据通过中序遍历插入到二叉树中,那么查找指定的数据就比较好找了。可以知道查找指定数据的时间复杂度跟树的深度有关,如果二叉树比较平衡,那么查找一个指定数据的时间复杂度为log2n,但是有不平衡的二叉树,搜索效率就没那么好了,比如二叉树极度不平衡,使之成为了一个“链表”结构,那么搜索效率就是最低的了,因此我们需要找到一种方式使得二叉搜索树比较平衡。

AVL树(平衡搜索二叉树):
(1)平衡因子factor{-1, 0, 1},即节点右子树的深度 - 左子树的深度要保持在factor范围,否则将不是平衡树;
(2)那么对于不平衡的树,需要使用左旋,右旋,左右旋,右左旋 4个旋转操作来保证二叉树是平衡的。
右右子树:左旋操作;
左左子树:右旋操作;
左右子树:左右旋操作;
右左子树:右左旋操作。

红黑树(近似平衡搜索二叉树):
(1)根节点是黑色;
(2)相邻的两个节点不能都是红色的;
(3)从任意一个节点看其子树的叶子节点路径下,黑色节点的个数相同;
(4)每个叶子节点都是空节点,都是黑色节点。
红黑树能够保证任意节点的左右子树的深度差要小于2倍,这样就可以保证这样的搜索二叉树不会极度的不平衡,性能不会极度差。

AVL树与红黑树对比:
(1)AVL树查找的效率要比红黑树的效率高,因为AVL保证严格的平衡,而红黑树只是近似平衡;
(2)红黑树的插入和删除效率要比AVL树效率高,因为AVL为了保持平衡,导致旋转的次数更多;
(3)AVL占用的控件更大,因为AVL的每个节点需要使用一个int数据来存储平衡因子,而红黑树每个节点只需要一个bit来存储是否是红色或黑色;
以上可以得出,如果读取操作比较频繁的话就选择AVL树,如果插入和删除的操作比较频繁的话就选择红黑树。一般在高级语言的Map、Set中都是使用的红黑树,而在数据库中一般使用的是AVL树。

27.Android的Thread基本原理:

Thread:
Thread.start()和Thread.run()区别在于:start()是创建了线程,然后将Runnable运行在新的线程上,但是run()方法是直接在当前线程中执行Runnable。

Thread的成员nativePeer是long类型,是native层Thread的内存地址。

start()方法会调用Thread.nativeCreate()方法,通过调用jni调用native层的Thread类的静态方法创建native线程,Thread::CreateNativeThread() --》 pthread_create(),同时为这个Thread创建JNIEnvExt(继承自JNIEnv),创建成功后,调用Thread::CreateCallback()方法(注意这个方法执行在新的线程中),会通过jni调用java层Thread对象的run()方法。
也就是说Java层的Thread和native层的Thread都只是运行在新线程的程序而已,创建线程的方法是pthread_create()。

28.进程与线程:
使用adb shell ps 可查看所有的进程信息,其中pid是指当前进程号,ppid是父进程的进程号。

进程:
第一个被创造出来的进程是0号进程,这个进程在操作系统层面是不可见的,但它存在着。0号进程完成了操作系统的功能加载与初期设定,然后它创造了1号进程(init),init用来管理整个操作系统。
init进程是一个死循环,所有进程的创建都在这个循环里面。其中包括shell进程,这个进程是用于用户跟操作系统进行交流的。
进程,是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。它的执行需要系统分配资源创建实体之后,才能进行。

- 进程是资源分配的基本单位,线程是调度的基本单位:
线程是更小的可以调度的单位,也就是说,只要达到线程的水平就可以被调度了,进程自然可以被调度。它强调的是分配资源时的对象必须是进程,不会给一个线程单独分配系统管理的资源。若要运行一个任务,想要获得资源,最起码得有进程,其他子任务可以以线程身份运行,资源共享就行了。
简而言之,进程的个体间是完全独立的,而线程间是彼此依存的。多进程环境中,任何一个进程的终止,不会影响到其他进程。而多线程环境中,父线程终止(这个主线程其实就是有main函数的进程),全部子线程被迫终止(没有了资源)。而任何一个子线程终止一般不会影响其他线程,除非子线程执行了exit()系统调用。任何一个子线程执行exit(),全部线程同时灭亡。

- 创建方式:
进程的创建:pid_t fork(void); fork()是将父进程的全部资源复制给了子进程。
线程的创建:int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);clone只是复制了一小部分必要的资源,在运行时需要的时候再复制。这个函数是系统调用,我们一般使用库函数pthread_create();

29.ActivityRecord:

ActivityRecord并没有继承于Binder。 但ActivityRecord的成员变量appToken的数据类型为Token,Token继承于IApplicationToken.Stub

(1)ActivityRecord:
Activity的信息记录在ActivityRecord对象, 并通过通过成员变量task指向TaskRecord
- ProcessRecord app //跑在哪个进程
- TaskRecord task //跑在哪个task
- ActivityInfo info // Activity信息
- int mActivityType //Activity类型
- ActivityState state //Activity状态
- ApplicationInfo appInfo //跑在哪个app
- ComponentName realActivity //组件名
- String packageName //包名
- String processName //进程名
- int launchMode //启动模式
- int userId // 该Activity运行在哪个用户id

(2)TaskRecord:
Task的信息记录在TaskRecord对象.
- ActivityStack stack; //当前所属的stack
- ArrayList mActivities; // 当前task的所有Activity列表
- int taskId
- String affinity; 是指root activity的affinity,即该Task中第一个Activity;
- int mCallingUid;
- String mCallingPackage; //调用者的包名

(3)ActivityStack:
- ArrayList mTaskHistory //保存所有的Task列表
- ArrayList mStacks; //所有stack列表
- final int mStackId;
- int mDisplayId;
- ActivityRecord mPausingActivity //正在pause
- ActivityRecord mLastPausedActivity
- ActivityRecord mResumedActivity //已经resumed
- ActivityRecord mLastStartedActivity

(4)ActivityStackSupervisor:
- ActivityStack mHomeStack //桌面的stack
- ActivityStack mFocusedStack //当前聚焦stack
- ActivityStack mLastFocusedStack //正在切换
- SparseArray mActivityDisplays //displayId为key
- SparseArray mActivityContainers // mStackId为key

- 一般地,对于没有分屏功能以及虚拟屏的情况下,ActivityStackSupervisor与ActivityDisplay都是系统唯一;
- ActivityDisplay主要有Home Stack和App Stack这两个栈;
- 每个ActivityStack中可以有若干个TaskRecord对象;
- 每个TaskRecord包含如果若干个ActivityRecord对象;
- 每个ActivityRecord记录一个Activity信息。

30.事件分发机制(native到java):

(1)找到对应的Window是通过事件的坐标和Window的Z-order来决定的。

(2)其中Socket(C)是在ViewRootImpl.setView时创建的,并将其传入WindowInputEventReceiver的构造函数,然后通过nativeInit()本地方法将WindowInputEventReceiver对象和Socket(C)对象传入C++层保存并关联起来,等到Socket(C)接收到事件时,就会通过传入的WindowInputEventReceiver对象使用JNI调用其dispatchInputEvent()方法,这就是实现了事件数据从C++层到Java层的传递。

(3)服务端的inputChannel列表会根据WindowManagerService的Window添加、删除通过InputManagerService来注册和解注册InputChannel。

事件分发从native经过层层调用最终到Java层是调用
WindowInputEventReceiver.dispatchInputEvent()方法 -> WindowInputEventReceiver.onInputEvent() -> ViewRootImpl.enqueueInputEvent() -> ViewRootImpl.doProcessInputEvents() -> 这里会进入while()循环依次将事件链表中的事件作为ViewRootImpl.deliverInputEvent()的参数调用 -> ViewRootImpl.processPointerEvent() -> mView.dispatchPointerEvent(event)其中mView就是ViewRootImpl的mView,也就是DecorView,DecorView.dispatchPointerEvent() -> DecorView.dispatchTouchEvent():

DecorView.dispatchTouchEvent():

public boolean dispatchTouchEvent(MotionEvent ev) {final Window.Callback cb = mWindow.getCallback();return cb != null && !mWindow.isDestroyed() && mFeatureId < 0? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);}

其中mWindow.getCallback()是在Activity.attach()时,创建PhoneWindow,并调用PhoneWindow.setCallback(this),将Activity作为Callback传入到Window中,那么此时上面代码如果callback存在就执行callback的dispatchTouchEvent方法,也就是Activity.dispatchTouchEvent()。

接着上面来:

DecorView.dispatchTouchEvent() -> Activity.dispatchTouchEvent():

Activity.dispatchTouchEvent():

public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}

先调用Window的superDispatchTouchEvent方法,如果返回true,就不会执行Activity.onTouchEvent()方法了。

Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() -> DecorView的super.dispatchTouchEvent(event),也就是View.dispatchTouchEvent()方法,这样就进入了ViewGroup和View的事件分发了,接下来的大家都不叫清楚了,我就不再详细说了。

Android高级-笔记相关推荐

  1. Android高级终端开发学习笔记(《疯狂Android讲义》第11章-第17章)

    Android高级终端开发笔记 2021/6/19 下午 13:34开始 多媒体应用开发 Android支持的音频格式有:MP3 WAV 3GP等.支持的视频格式有MP4 3GP等. 多媒体数据既可以 ...

  2. 《Android高级进阶》读书笔记

    <Android高级进阶>是据我所知的市面上唯一一本技术工具书,比较的高大全,作者的目的是为了对全领域有个初步的概念 No1: 在Android系统中,拥有事件传递处理能力的类有以下三种 ...

  3. Android高级终端开发学习笔记(《疯狂Android讲义》第2章-第10章)

    Android疯狂讲义前10章知识点总结 /-------------------------10-31号晚上学习笔记----------------------------/ 在设置了最小支持版本为 ...

  4. 耗时118天爆肝【1296页】的“Android高级开发面试题”,终于成功上岸字节

    前言 本人16年毕业于一家普通二本,考研裂开了且没有实习经验,只做过两个项目,每天就是不断地投简历.刷面经,感觉自己都要抑郁了,最后勉强进入了一家学校合作的互联网公司,后面陆陆续续也换了几家公司,毕业 ...

  5. 这些Android高级必会知识点你能答出来几个?含BATJM大厂

    前言 首先介绍一下自己,计算机水本,考研与我无缘.之前在帝都某公司算法部实习,公司算大公司吧,然而个人爱好偏开发,大二的时候写个一个app,主要是用各种框架. 一.掌握架构师筑基必备技能 二.掌握An ...

  6. Android开发笔记(序)写在前面的目录

    知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经验教训,与网友互相切磋,从而去芜存菁进一步提升自己的水平.因此博主就想,入门的东西咱就不写了,人不能老停留在入 ...

  7. Android开发笔记(一百六十七)Android8.0的画中画模式

    前面的博文< Android开发笔记(一百五十九)Android7.0的分屏模式>介绍了Android7.0的多窗口特性,但是这个分屏的区域是固定的,要么在屏幕的上半部分,要么在屏幕的下半 ...

  8. Android开发笔记(一百二十六)自定义音乐播放器

    MediaRecorder/MediaPlayer 在Android手机上面,音频的处理比视频还要复杂,这真是出人意料.在前面的博文< Android开发笔记(五十七)录像录音与播放>中, ...

  9. Android开发笔记(一百零七)统计分析SDK

    APP统计分析 用户画像 对程序员来说,用户画像就是用户的属性和行为:通俗地说,用户画像是包括了个人信息.兴趣爱好.日常行为等血肉丰满的客户实体.用户画像是精准营销的产物,企业通过收集用户的行为,然后 ...

  10. Android开发笔记(九十八)往图片添加部件

    添加圆角 添加圆角的功能,要用到Canvas类的drawRoundRect方法,即把画布裁剪成指定的圆角矩形. 下面是给图片添加圆角的效果截图: 下面是给图片添加圆角的代码片段: public sta ...

最新文章

  1. 用R语言做词频统计_R语言 | 词频统计
  2. deepin如何布署python_【玩转deepin】简单三步,教你在deepin15.11上安装Python3.7.4
  3. java成员变量的初始化
  4. linux tcb,在Linux中从潜藏密码迁移至tcb怎么做?
  5. “七大属性加持,三个全新升级组件”这个高性能利器有点厉害
  6. 局域网远程yum源制作
  7. Sublime Text 3 插件安装及Vim 模式设置
  8. 转载 java抽象类与接口的区别
  9. 安装项目管理工具 SVN+Redmine
  10. Dynamic CRM 2013学习笔记(四)单据编号及插件批量注册工具
  11. 明晰监管范围保护信息安全
  12. matlab gui 毕业论文,MATLAB GUI课件设计
  13. win10查看服务器共享文件夹,Win10怎么样查看共享文件夹
  14. html投影电脑,无线投影小PC 联想投影电脑610S评测
  15. APOLLO基本介绍
  16. Andersen Global在南非拓展业务
  17. 【Image Matting】Image Matting评价指标
  18. android nv位图打印机_通过C#中的comport将位图图像打印到pos打印机
  19. 【大学生Python】字典的基础使用
  20. 一键分析Android的BugReport

热门文章

  1. java pgm_如何用Java读取PGM图像?
  2. 国内外Java学习论坛汇总
  3. 基于Javaweb的社区门诊系统的设计与实现MIB信息采集
  4. 前端学习之路-聚美优品注册页面的实现
  5. hugo博客html创建目录,1构建个人博客--使用Hugo快速成型(示例代码)
  6. apk修改android开机画面,Android手机开机动画的修改
  7. [内附完整源码和文档] 基于VS2013实现的弹弹堂小游戏
  8. Flash鼠标拖尾效果——粒子
  9. kernel支持4k/16k/64k pagesize
  10. 计算机考试系统注册组件失败,银行从业在线支付,IE浏览器安装控件不成功怎么办...