Map
循序表(数组arraylist)和linklist ( 双向链表)链表的插入、删除、查询的效率?
如果一个数组反复插入删除怎么降低时间复杂度?
A.数组插入、删除:是在下标i的地方,后边的数组整体system.copy一份,然后插入到i位置。Copy是一个循环。
链表插入、删除:是一个节点node,node包含两部分,数组和指针,当前指针指向下一个数组。
数组,查找快,直接用下标获取。
链表,查找慢,需要循环获取。
B.标记-查找:删除的时候不删除,赋值为null或者标记其他,这个下次在插入的时候直接赋值即可。然后在适当的机会,循环删除值为null的数组即可。这样就可以优化了对象的反复创建和删除。
C.linkedlist,查询第一个和最后一个时间复杂度一样?一样,查询第一个和倒数第二个一样吗?不一样,liankedlist是双向链表。倒数第二个需要两步。
hashmap?长度默认16
jdk 1.7:为什么用链表?用的是hash碰撞原理
A。把key通过hashcode对length取模,设置下标。所以hashmap的table表是顺序的。然后创建一个节点(数据hashmapentry),放到这个下标的位置。同时每个下表对应的所有节点是一个单链表。
B。Hashcode的获取,就是一个hash碰撞的过程。比如hashcode=1和hashcode=17,是在一个下标上。
C。Get一个数据,就是用过key获取链表,然后在通过key循环具体的值,循环过程中要判断hashcode一致,判断key一致。
D。Hash表在什么情况性能最差?所有数据在一个节点上,那链表久非常大,效率就非常低了。
E。什么长度是16?因为在hash下标的过程中。16的二进制是10000.
jdk1.8:每一个节点,用了红黑树。Treenode。但不是每一个节点都是红黑树。
A。hash冲突后不在用链表保存相同的index节点,而是采用红黑树保存冲突节点。
B。红黑树构建的时候很复杂、效率很低(旋转)。所以当链表超过8个才使用红黑树。
hashmap线程安全问题:不安全。
如果链表被同时操作就会出现问题。
Hashtable线程安全;所有函数(put、get,remove)的时候用到了synchronized。但是这样的任何访问都加了锁,包括所有hashcode上。
Synchronized,内置锁,开锁和关锁都是jvm完成的。所以get时候不能put。只能执行其中一个。
Concurrenthashmap:是基于hashtable的优化,它是在具体的链表上锁,也是用到了synchronized,但是它只是锁在了链表上。
广播注册方式?有什么不同?通信原理是什么?
动态,静态。原理都是Binder。静态广播不需要手动取消注册,周期是整个app期间。动态广播需要。
hashmap和sparsearray哪个更优?
Hashmap:hashcode操作,耗时。Hashmap是任意对象。装载因子(阀值)是0.75。达到16*0.75=12,开始扩容,也是耗时操作。浪费了4个空间,所以耗内存。Get是遍历获取链表,耗时。
Sparsearray:两个数组:默认空间10;key[],value[]. Key与value是一一对应的。Sparsearray是int,节省内存空间。对应的key应该存在数组的哪里?采用了二分查找,找到数组中的位置,然后通过system。Copy移动数组插入。
get?通过二分查找找到下标,然后就可以了。速度快。
拷贝效率也低啊?但是这里,remove时是没有具体删除的,而是标记一个delete。这样插入的时候直接赋值就可以了。而hashmap是循环。
所以,sparsearray时间更快。但是hashmap空间更小。
单例模式:
A:双重校验:
如何防止指令重排,添加volatile。
B:懒汉式单利
线程不安全
线程安全
但是同步锁粒度太大:下边的粒度就小很多;
C.饿汉式单例:线程安全
D.静态内部类单例:线程安全的:
E.采用枚举:枚举默认线程安全;
F.使用容器实现单例:
string 的== 与equals的区别?
String str1 = “a”常量池是在堆内存里的。==比较的是内存存放的位置。但是他们对hanshcode是相等的。
equals比较的是字符串的序列,是具体的字字符比较。
Stirng str = “阿”。str = str+“阿”。与string ds = new string (“阿阿”);str和ds是一个对象,string是不可变的,因为string类里的方法,都是final。
volatile:
A.多线程可见
所以,volatile就相当于一个总线嗅探机制,当其中一个线程变化后,会修改主内存的值,其他线程会被通知,从新从主内存获取数据。并且8个操作是原子性的,其中一个指令执行,其他不会操作的。原理是:当一个线程执行write时,是被加了锁的(如下图的lock)。
B:禁止指令重排(半初始化),原理是内存屏障。
双重检测的单例,为啥加volatile?就是禁止指令重排。顺序如下:
但是在cpu执行过程中,这个顺序会改变。如果在单线程里,这个顺序改变了也没关系,但是在多线程中,如果在一个线程创建了引用,而另一个线程在执行时,就会拿到这个引用,从而,拿到的值有可能是第一个线程未赋值的初始值0,而第二个还在初始化对象。
锁
上述代码:多线程并发问题。会出现i–,最后不为0的情况。
A.synchronized:内置锁,重量级锁
B.AtomicInteger:乐观锁:无锁机制.
比synchronized快。缺点是:i.decrementAndGet的i—是可操作的。
原理:根据堆内存中的偏移量来获取value,比通过对象.value的拷贝方式要快。
C.ReentrantLock:可重入锁,也叫显示锁
是接口lock锁的实现类。也可以自己实现公平锁或者不公平锁,因为队列线程是我们自己的。也是重量级锁。
原理、执行过程:
D.CAS:先比较再交换。也是一种乐观锁,无锁机制。
高并发不适合。但是如果只有几个线程,速度非常快。
而且CAS有ABA问题:尤其是对象操作的时候。
Context是什么?抽象类
A.上下文,贯穿整个应用。
B.运行环境:提供了一个应用运行所需要的信息、资源、系统服务等。
C.场景:用户操作和系统交互这一过程就是一个场景。比如Activity之间的切换、服务的启动等都离不开Context。
什么是Activit、View、Window?
Dex文件
APK打包流程
Xml通过aapt编译成R.JAVA
Aidl通过aidl文件生成java文件
然后通过javaCompiler把java文件转化为class文件。
然后通过dex把class打包成.Dex文件。
用apkbuild把所有文件,包括资源,图片文件通过zip压缩打包成apk,然后通过对齐,签名形成一个可运行的apk。
事件分发处理机制
事件处理
Down事件的分发流程
measureSpec是什么?原理是什么?
测量主要解决parent和wrap的测量。
FlowLayout-》onMeasure:
//计算孩子的宽/高:
Width=100;height=300;
//设置自己的宽/高:
setMeasureDimension(width,height)
那如何计算孩子的宽高?如何度量?其中view的大小,不是int,而是MeasureSpec。
所有通过childView.getMeasureWidth(),childView.getMeasureheigth(),但是需要在获取之前设置度量孩子的大小childView.measure(widthMeasureSpec,heightMeasureSpec), widthMeasureSpec,heightMeasureSpec是什么?可以认为是父类宽高,childView的最大宽度,就是父类的宽高减去padding。那MeasureSpec如何得到?因为当前的子view也有可能有子view,所以.measure的时候,还需要通过getChildMeasureSpec获取widthMeasureSpec,heightMeasureSpec。
通俗的说?widthMeasureSpec,heightMeasureSpec就是能给chaildView的具体的大小。
那为什要measure?因为只有把父类的具体大小给孩子,才能测量出孩子的具体宽高。
layout自定义View布局原理?
就是确定每一个view的left,top,right,bottom。
onLayout是用的视图坐标系:因为你布局的时候,是根据父布局进行布局的。
Layoutparams保存了view的坐标。
##
inflate解析xml过程?
Xml-》解析-?然后创建view,通过反射创建,比如自定义view在xml中使用,需要全路径名-》创建view后,会把解析的属性给view。
沉浸式开发原理?状态栏和ui一致背景?
ViewPage?默认缓存3个界面。
Setoffscreenpagelimit(1), Limint为1.他的意思是最多缓存当前界面的左右两个界面,加上当前就是3个界面。
预加载:问题?缓存,会耗费更多内存,时间?会耗费更多时间。
所以采用懒加载方案,就是loading或者白屏的时候。
所以解决这个问题的原理:populate:填充+fragment adapter适配器模式。
缓存:就是创建fragment放入到arraylist里。
适配:当左右滑动时,先处理左边或右边的界面?如何处理,初始化(instantiateItem)一个fragment(放入arraylist)和销毁(destroyItem)掉一个fragment(从arraylist中删除)。
Attch,fragment不会立即执行。这个时候,首先还要把刚初始化的页面设置为不可见setUerVisibleHint,也就是即将要缓存起来的item不可见。
看下图:首先要缓存tab4,把tab1设为不可见,设置当前的目标tab3设为可见,并且直接用的FragmentCompat,。但是这个可见是在finishUpdate的commit之前执行的,所以,viewpage的fragment的生命周期,第一个是setUserVisibleHint。如下下图。
setPrimaryItem会执行两个动作:之前的item设置为不可见,新的item设为可见。
只有执行fragment的CommitAllowingStateLoss之后才会走fragment生命周期。
RecycleView
缓存和复用都是ViewHolder。其实就是一个itemView。那缓存有几级?
以下就是处理复用机制:
从滑动看复用填充流程:
从onLayout看复用填充流程:最终也会到fill入口,跟之前一样。
缓存?=========================================================================
OOM是如何发生的?
Java回收机制?如何减少OOM的概率?
如何排除应用崩溃原因?ANR?
1.把崩溃堆栈传到服务。
使用第三方开源库。
App启动优化,速度优化?
热修复原理?
AndFix:
Robust:
Tinker&类加载机制:
Bsdiff差分工具:
如何拿当前的apk问价:PackageManager
CalssLoader:
自己写的类,用的PathClassLoader(mainactivity),系统的类,就是BootClassLoader(application,activity)。
加载一个类方法:PathClassLoader.loadClass();->BaseDexClassLoader->ClassLoader.loadClass(),所以通过一个类全路径,获取Class对象。
所有的类在dex文件里。FindLoadedClass从缓存获取class对象。如果没有,在通过parent .loadClass();parent也是一个ClassLoader。这里用的就是双亲委托机制。为什么要有这个机制:一是安全,而是避免重复加载。但是如果是我们的自己的类,parent肯定加载不到,所以执行findClass()。而这个classloader,是pathclassloader。所以最后看到baseDexClassLoader。
然后看到,通过for循环找,dexElements,是一个数组【class1.dex,class2.dex,。。。。。。】。从而拿到了dex中的Class类对象。所以,我们得到了修复之后的dex,那怎么办,只要把这个dex放到数组的最前面即可,因为是顺序的,所以在使用的时候,拿到这个正确的之后,就直接return了,不会在查询class2.dex……..。最后,先getClassLoader拿到pathclassloader,通过反射,拿到DexPathList,在拿到这个类里的dexElements数组,把这个dex,放到dexElements里即可。
双亲委托机制:
在用户的加载类里,如果要加载Activity系统类,这个时候,不需要自己加载,由parent=BootClassLoader加载了。
Qzone:
如何判断文件的一致性?
crc方法。CRC32 即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC )是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
多进程-文件共享
Messenger,Bundle,文件共享,Content Provider……
sp、mmkv、mmap
SharedPreference:
map持久化: 在内存中时,在文件中序列化是xml。
初始化:在子线程里,通过io,获取xml文件,反序列化成map。
如果数据比较大时:读写数据时,awaitLoadedLocket:等待加载完成。所以数据大,需要解析时间长,如果没有解析完,就读写,这个时候需要等待子线程初始化数据完成。
并且sp创建后无法被回收。
写新数据:map-》序列化xml,写入文件。commit同步提交,会阻塞调用线程。Apply异步,无法获取结果,没有事务性api。无法保证数据一致性。同时哪怕异步提交都可能会导致ANR的发生。
更新数据:sp没有更新的概念,是整体的读写操作。哪怕只修改了1条数组,sp也需要全部序列化为xml,覆盖文件进行保存。(全量更新)
优化点:
高效的文件操作;MMAP;
数据格式更精简;二进制;
String一个字节占8位,如“1”(0000 0001)浪费了7位,其实只需要1位就可以;而二进制,一个字节就可以8个数据;
更优的数据更新方式;
FileChannel:
拷贝文件比IO快6-7倍。为什么这么快?底层用到了零拷贝技术;如sendfile,mmap。
MMKV:速度非常快。并且实现了sp和sp.editor接口。用了mmap技术。
零拷贝:是不需要CPU参与的技术。DMA COPY是从内存拷贝到Disk。
映射内存是通过Vma:vm_area_struct-》内存结构。
HTTP2:多路复用。
http1=字符串、文本
http2=二进制。
总长度(key长度+key值+value长度 +value值 ……)长度数据不一定是一个字节。
那map数据存到文件的过程要通过序列化。
那数据格式Protobuf:比json更加精简。Protobuf编码是一个变长编码,就是有可能把int原有4个字节,编码成1,2,3,4,5个字节。
Serializable
1. 不会。
2. 深拷贝。Equal
3. Budle用的是Parcel,为什么不用map,bundle是跨进程传输一些小的对象。
4. 版本控制。
5. 原理:binder,binder大小就是1M-8kb;binder创建的线程池默认15个(我记得是31个)。内核中映射的最大内核空间,只有4M。有没有办法Intent突破1M?mmap的大小可以修改,但是不可以超过4M.还可以把数据写入数据库或者文件,传递数据的文件名。
6. Activity的启动流程要和AMS打交道,是要跨进程。两个不同进程,数据只有序列化之后才能带过去。
7. 序列化是跨进程通信使用的。持久化是保存数据。
Glide
面试题:为什么glide不能再子线程中with?再子线程中不会添加生命周期管理,只有再主线程中才会用空白的Fragment监听Activity或者Fragment的生命周期。看下面源码:4.11
正在显示的是活动缓存,然后是内存缓存。
注解
所有javac处理自定义注解类,也是通过ServiceLoader(spi)的方式处理生成实现类的。
SPI
ASM字节码插桩
动画
插件化
类加载机制:双亲委派机制
findClass流程
合并第三方Dex
思路:
Hook Activity
代码流程:
先实现
再实现
版本适配
资源加载
资源插件:创建
解决资源文件冲突:
具体冲突:改变插件Context的mResources
开机启动流程
Loader
从按下电源开始,会通过Boot Rom中的引导芯片代码开始运行。加载引导程序BootLoader到RAM,然后执行。BootLoader会引导操作系统启动。
Kernel
首先启动内核态(idle pid=0),初始化进程管理、内存管理、驱动(Binder,Display,Camera等等)相关工作,并创建内核进程kthreadd(pid=2),同时创建工作的线程kworkder,软中断线程等等守护进程等。这些都在内核态中进行。
init
然后启动用户进程init进程,也是第一个用户进程。从而手机进入了用户态。同时init进程是用户进程的鼻祖。每一个进程都有一个main函数。通过linux一系列执行,最终会进入init.cpp,通过LoadBootScripts方法解析init.rc文件,init会挂载一些文件,通过while循环,启动zygote,ServiceManager进程,音频进程,Camera进程等等。看如下图,包括usb,hardware等等。所以init进程主要启动我们后台需要的一些服务进程。while是一个死循环,保证进程的保活,进程无事可做,就通过epoll机制,休眠。
zygote
启动入口:app_main.cpp中main函数(android 10,11,12),再main函数中构建了AppRuntime,然后在runtime设置进程名,属性,然后会执行start;如下图
在start里,发现启动了vm虚拟机(通过startVm),并设置了vm的参数,如下图:
设置好参数后,接着就创建虚拟机(通过CreateJavaVm方法)
虚拟机作用是什么?管理内存。
虚拟机创建完了,接着通过startReg方法注册android常用的JNI;JNI是啥,就是nativie中间件,用来函数的静态(动态)注册。
接着执行zygoteInit.java的main函数:
在zygote中通过preload预加载函数,会预加载系统类和资源(ImageView、TexetView,Resource)等。
zygoteserver就是为zygote创建了一个socket服务器。
这时候zygote已经做了很多事情了,接下来该创建SystemServer(大儿子),并通过反射方式调用进程初始化的main函数;
接着进入循环等待过程,如下图:
SystemServer
SystemServer通过SystemServiceManager管理启动一系列系统服务SystemService(AMS,WMS,ATMS,PMS),而系统服务创建的时候就会创建一个Binder添加到ServiceManager里.
ServiceManager是一个守护进程,独立进程,只是存放了服务的Binder。
两者不一样,SystemService通过ServiceManager暴漏给上层app。
app进程
WMS原理
Ursprünglicher Link: http://nunu03.github.io/2022/05/29/Android知识点整理/
Copyright-Erklärung: 转载请注明出处.