1.热修复的几种方式
1.通过更改dex加载顺序实现热修复
热修复是基于dex分包方案,和Android虚拟机的类加载器(ClassLoader)实现的
在打包apk的时候,会把java文件通过类加载器编译成class文件,然后把class文件组合成class.dex文件,dex文件会把每一个类的id检索起来,存在一个链表里面。将编译好的class文件,拆分成多个dex文件,将应用启动时必须用到的类和这些类的应用类放到主dex文件,其他代码放到次dex文件
Android中的 Java.lang.ClassLoader类也不同与java中的Java.lang.ClassLoader类,加载的是dex文件
当分包完成之后,会形成一个dex包的有序数组,当需要加载类加载器的时候,会从数组中第一个dex包开始加载,直到找到这个类为止
在apk安装的时候系统会将dex文件优化成odex文件,在优化的过程中会涉及一个预校验的过程
如果一个类的static方法,private方法,override方法以及构造函数中引用了其他类,而且这些类都属于同一个dex文件,此时该类就会被打上CLASS_ISPREVERIFIED
如果在运行时被打上CLASS_ISPREVERIFIED的类引用了其他dex的类,就会报错
正常的分包方案会保证相关类被打入同一个dex文件
想要使得patch可以被正常加载,就必须保证类不会被打上CLASS_ISPREVERIFIED标记。而要实现这个目的就必须要在分完包后的class中植入对其他dex文件中类的引用
要在已经编译完成后的类中植入对其他类的引用,就需要操作字节码,惯用的方案是插桩。常见的工具有javaassist,asm等
Tinker的思路是这样的,通过修复好的class.dex 和原有的class.dex比较差生差量包补丁文件patch.dex,在手机上这个patch.dex又会和原有的class.dex 合并生成新的文件fix_class.dex,用这个新的fix_class.dex 整体替换原有的dexPathList的中的内容,可以说是从根本上把bug给干掉了。
AndFix 提供了一种运行时在Native修改Filed指针的方式,实现方法的替换,达到即时生效无需重启,大手机厂商定制了自己的ROM,所以很多底层实现的差异,导致AndFix的兼容性并不是很好
Sophix 便取长补短,采用全量替换的思路,从一种更高的层次实现了热修复
https://www.jianshu.com/p/16ed4d233fd1
2.app瘦身
1.相对于多套资源,只使用720P的一套资源,差别不大
2.混淆proguard中,是否保留符号表,可不保留
3.删除无用的语言资源,gradle设置
4.使用tinypng有损压缩
5.非透明大图使用jpg格式
6.微信Android资源混淆打包工具,采用7zip对APP进行极致压缩
7.使用着色方案,减少只是颜色不同的图片,例如selector
2.Android各版本新特性
https://www.jianshu.com/p/c4f1bf460c8b
3.同步异步方式小结
标准 I/O
应用程序平时用到 read/write 操作都属于标准 I/O,也就是缓存 I/O(Buffered I/O)。它的关键特性有:
对于读操作,当应用程序读取某块数据时,如果这块数据已经在页缓存中,那么就不需要经过物理读盘操作。
对于写操作,应用程序会先将数据写到页缓存中去,不需要等全部数据被写回磁盘,系统会定期将页缓存中的数据刷到磁盘上。
mmap
mmap 把文件映射到进程的地址空间,提高了 I/O 的性能。
mmap 的优点有:
减少系统调用。只需要一次 mmap() 系统调用,后续所有的调用像操作内存一样。
减少数据拷贝。mmap 只需要从磁盘拷贝一次,由于做过内存映射,不需要再拷贝回用户空间。
可靠性高。mmap 把数据写入页缓存后,跟缓存 I/O 的延迟写机制一样。
存在的缺点:
虚拟内存增大。Apk、Dex、so 都是通过 mmap 读取。mmap 会导致虚拟内存增大,mmap 大文件容易出现 OOM。
磁盘延迟。mmap 通过缺页中断向磁盘发起真正的磁盘 I/O,不能通过 mmap 消除磁盘 I/O 的延迟。
在 Android 中可以将文件通过 MemoryFile 或者 MappedByteBuffer 映射到内存,然后进行读写,使用这种方式对于小文件和频繁读写操作的文件还是有一定优势的。
Direct I/O
直接 I/O 访问文件方式减少了一次数据拷贝和一些系统调用的耗时,很大程度降低了 CPU 的使用率以及内存的占用。负面影响就是读写操作都是同步执行,导致应用程序等待。
NIO 是非阻塞 I/O,将 I/O 以事件的方式通知,可以减少线程切换的开销。NIO 的最大作用不是减少读取文件的耗时,而是最大化提升应用整体的 CPU 利用率。
4.eventbus
Event:事件,它可以是任意类型,EventBus会根据事件类型进行全局的通知。
Subscriber:事件订阅者,在EventBus 3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe,并且指定线程模型,默认是POSTING。
Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。
四种线程模型
1.POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。
2.MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。
3.BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
4.ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。
使用流程
接收者复写
doCreateView 注册监听
onDestroy
onGetMessage 用@Subscribe注解指定线程模式,priority指定优先级
onGetStickyEvent 获取粘性广播,即注册接收器后也能收到之前的广播 对应的发送为postSticky
发送者 post message
regist
你只要记得一件事:扫描了所有的方法,把匹配的方法最终保存在
subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> )中;
eventType是我们方法参数的Class,Subscription中则保存着
subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切。
post会根据实参去map查找进行反射调用postToSubscription,然后判断应该在哪个线程去执行该方法
5.死锁
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
解决死锁的基本方法
预防死锁:
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
6XML
有DOM、SAX、PULL三种解析方式
DOM是基于文档驱动的方式。可用于直接访问 XML 文档的各个部分。
它是一次性全部将内容加载在内存中,生成一个树状结构,它没有涉及回调和复杂的状态管理。缺点是加载大文档时效率低下。
SAX使用流式处理的方式。是以事件为驱动的XML API,使用回调函数来实现。优点是解析速度快,占用内存少。缺点是不能倒退。
PULL内置于 Android 系统中。也是官方解析布局文件所使用的方式。
Pull 与 SAX 有点类似,都提供了类似的事件,如开始元素和结束元素。不同的是Pull解析器并没有强制要求提供触发的方法。因为他触发的事件不是一个方法,而是一个数字。它使用方便,效率高。
7.Asset目录与res目录的区别
assets:不会在 R文件中生成相应标记,存放到这里的资源在打包时会打包到程序安装包中。(通过 AssetManager 类访问这些文件)
res:会在R文件中生成 id标记,资源在打包时如果使用到则打包到安装包中,未用到不会打入安装包中。
8.序列化,
https://blog.csdn.net/qq_25722767/article/details/51879120
表示将一个对象转换成可存储或可传输的状态。
需要序列化的原因有三种情况:
永久性保存对象,将对象的字节序列存储到本地文件中;
对象在网络中传递;
对象在IPC间传递。
9.Recyclerview和Listview的区别
在ListView中,ViewHolder需要自己来定义。通过ViewHolder可以缓存item里的view控件实例,避免了在getview中重复创建带来的性能损耗,但这只是一种推荐的使用方式,不是必须使用的。而在RecyclerView中使用RecyclerView.ViewHolder则变成了必须,尽管实现起来稍显复杂,但是在性能提升上有很大的好处。
ListView只能在垂直方向上滚动,Android API没有提供直接让ListView在水平方向上面滚动的支持。但RecyclerView提供了多种类型的展示方式,很容易就能修改展示方式。
。
ListView对item的点击事件实现较为简单,Recyclerview的点击事件实现就相对复杂,但灵活性高。
ListView没有提供局部刷新,RecyclerView提供了局部刷新的方法,而且在局部刷新的时候有一个渐变的动画效果。
其实就是使用好带payload参数的这个notifyItemChanged方法,以及重写带payload的这个onBindViewHolder方法,在onBindViewHolder中去刷新你想更新的控件即可。
10.SparseArray
SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间。
SparseArray在存储和读取数据时候,使用的是二分查找法。
数据量不大,最好在千级以内
key必须为int类型,这中情况下的HashMap可以用SparseArray代替
11.对称加密 非对称加密
对称加密:A与 B 之间之间的通讯数据都用同一套的密钥来进行加密解密。
优点
简单快捷,密钥较短,且破译困难。
缺点
如果用户一旦多的话,管理密钥也是一种困难。不方便直接沟通的两个用户之间怎么确定密钥也需要考虑,这其中就会有密钥泄露的风险,以及存在更换密钥的需求。
对称加密通常有 DES,IDEA,3DES 加密算法。
非对称加密:用公钥和私钥来加解密的算法。打个比方,A 的公钥加密过的东西只能通过 A 的私钥来解密;同理,A 的私钥加密过的东西只能通过 A 的公钥来解密。顾名思义,公钥是公开的,别人可以获取的到;私钥是私有的,只能自己拥有。
缺点
加解密比对称加密耗时.
优点
比对称加密安全.
但是非对称加密也是存在漏洞,因为公钥是公开的,如果有 C 冒充 B 的身份利用 A 的公钥给 A 发消息,这样就乱套了,所以接下来就采用非对称加密+摘要算法+数字签名的机制来确保传输安全。
常见的非对称加密算法有:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
12 listview下拉刷新
13 LRU
LruCache(Least Recently Used)算法的核心思想就是最近最少使用算法。
由此可见LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即最近最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。
sqlite cache策略
SQLite缓存替换算法是LRU(Least Recently Used,最近最少使用算法)。实现比较简单,主要是由两部分组成:
hash表。hash表主要是加快对缓存中数据页的查找速度。hash表后面是一串链表,保存满足该hash函数的所有的页。SQLite是通过页号来进行hash操作的,hash完找到链表的头结点,然后依次查找。
LRU链表。LRU链表是通过SQLite操作hash表中的元素的来实现的。SQLite对hash表中页进行一次操作,就会将该页放到LRU链表的头部,因为该页是最近最常用到的。如果缓存需要替换,则需要从LRU链表尾部取出,然后回写到数据库文件中。
14gradle的工作原理
有一个语言叫groovy ,咱用它制定一些规则,来完成上面所说的操蛋的构建的事。这种新的脚本语言就是gradle
15.GreenDao数据库
GreenDAO是一个开源的Android ORM(“对象/关系映射”),通过ORM(称为“对象/关系映射”)
DaoMaster::DaoMaster保存数据库对象(SQLiteDatabase)并管理特定模式的DAO类(而不是对象)。它有静态方法来创建表或删除它们。它的内部类OpenHelper和DevOpenHelper是SQLiteOpenHelper实现,它们在SQLite数据库中创建模式。
DaoSession:管理特定模式的所有可用DAO对象,您可以使用其中一个getter方法获取该对象。DaoSession还提供了一些通用的持久性方法,如实体的插入,加载,更新,刷新和删除。
XXXDao:数据访问对象(DAO)持久存在并查询实体。对于每个实体,greenDAO生成DAO。它具有比DaoSession更多的持久性方法,例如:count,loadAll和insertInTx。
Entities :可持久化对象。通常, 实体对象代表一个数据库行使用标准 Java 属性(如一个POJO 或 JavaBean )。
16,AOP面向切片编程
AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。
AOP在安卓中主要是指把某一方面的一些功能提取出来与一批对象进行隔离,提取之后我们就可以对某个单方面的功能进行编程,如请求网络时,需要先判断网络是否连通
17.sf
这两个方法的区别在于:
1. apply没有返回值而commit返回boolean表明修改是否提交成功
2. apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
3. apply方法不会提示任何失败的提示。
18java虚拟机和dalvik虚拟机
Dalvik和Java之间的另外一大区别就是运行环境——Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个 Dalvik应用作为一个独立的Linux进程执行。
(1)虚拟机很小,使用的空间也小;
(2)Dalvik没有JIT编译器;
(3)常量池已被修改为只使用32位的索引,以简化解释器;
(4)它使用自己的字节码,而非Java字节码
19 数据库隔离等级
数据库四个隔离级别为read uncommitted,read committed,repeatable read,serializable,依次解决脏读,不可重复读,幻读问题。
read uncommitted:写事务阻止其他写事务,避免了更新遗失,但是未阻止读事务。
read committed:写事务阻止读写事务,读事务不会阻止其他事务。解决脏读问题
repeatable read:读事务会阻止其他写事务,但不会阻止其他读事务。解决不可重复读问题
serializable:读加共享锁,写加排他锁。读事务可并发。但是读写,写写互斥,基本上是一个个执行事务,所以叫做串行化。解决幻读问题
脏读:读取到未提交数据,导致获取到不准确数据。
不可重复读: (同一个事务中)同一select语句,两次读取到已提交数据,数据内容(数据信息)不一致。
幻读:(可以不是同一事务)同一select语句, 两次读取到已提交数据,数据内容(数据条数)不一致。
23.observer模式的应用和 update的区别
24 rxjava的理解
RxJava 的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁。
https://www.jianshu.com/p/b1ab67ebdfca
25.dagger2+rxjava+retrofit2+mvp
dagger2的优势,省去无谓的体力劳动,增加开发效率,代码解耦
rxjava的优势,尽管项目里的逻辑不断的变为复杂,但是rxjava异步代码依然简洁易懂。
retrofit2的优势,简洁功能却强劲,自定义GSON解析,添加拦截器等
mvp的优势,结构清晰,代码解耦,维护性较高
26 保活
常用的保活方式
Activity提权
Service提权
广播拉活
“全家桶”拉活
Service机制(Sticky)拉活
账户同步拉活
JobScheduler拉活
推送拉活
Native拉活(NDK)
双进程守护(JobScheduler和两个Service结合用)
27 glide
现在Android上的图片加载框架非常成熟,从最早的老牌图片加载框架UniversalImageLoader,到后来Google推出的Volley,再到后来的新兴军Glide和Picasso,当然还有Facebook的Fresco。每一个都非常稳定,功能也都十分强大。但是它们的使用场景基本都是重合的,也就是说我们基本只需要选择其中一个来进行学习和使用就足够了,每一个框架都尝试去掌握的话则有些浪费时间。Glide和Picasso 有90%相似度,而Glide在Picasso基础上进行的二次开发,可以其优势显而易见。
两级内存缓存:先从LruCache中寻找,如果找到了缓存,将图片移出LruCache,加入activeResources弱引用缓存。如果在LruCache中没找到的话到activeResources弱引用缓存中寻找。如果在内存缓存中找到,则引用计数加1。使用中的图片用弱引用缓存来管理,没有使用的图片用LruCache来管理,判断图片有没有使用的依据之一是引用计数,当引用计数等于0时,将图片从弱引用缓存中移走,加入LruCache中。
https://www.jianshu.com/p/7b1ff697b06f
28 butterknife
ButterKnife是一个专注于Android系统的View注入框架,以前总是要写很多findViewById来找到View对象,有了ButterKnife可以很轻松的省去这些步骤。是大神JakeWharton的力作,目前使用很广。最重要的一点,使用ButterKnife对性能基本没有损失,因为ButterKnife用到的注解并不是在运行时反射的,而是在编译的时候生成新的class。项目集成起来也是特别方便,使用起来也是特别简单。
29git
rebase会把你当前分支的 commit 放到公共分支的最后面,所以叫变基
不要在公共分支使用rebase
本地和远端对应同一条分支,优先使用rebase,而不是merge
为什么不要再公共分支使用rebase?
因为往后放的这些 commit 都是新的,这样其他从这个公共分支拉出去的人,都需要再 rebase,相当于你 rebase 东西进来,就都是新的 commit 了
设计模式
https://www.cnblogs.com/davidwang456/p/11328433.html
工厂模式
https://www.runoob.com/design-pattern/factory-pattern.html
观察者模式
https://www.runoob.com/design-pattern/observer-pattern.html
生产消费模式
https://blog.csdn.net/Virgil_K2017/article/details/89283946
mvp
https://blog.csdn.net/pizifusheng/article/details/80948341
单例模式
https://www.jianshu.com/p/ad86f107425c
apk打包过程
过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java文件。
通过AIDL工具处理AIDL文件,生成相应的Java文件。
通过Javac工具编译项目源码,生成Class文件。
通过DX工具将所有的Class文件转换成DEX文件,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
通过ApkBuilder工具将资源文件、DEX文件打包生成APK文件。
利用KeyStore对生成的APK文件进行签名。
如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件举例文件的起始距离都偏移4字节的整数倍,这样通过内存映射访问APK文件
的速度会更快。
APK的安装流程
资源管理器解析APK里的资源文件。
解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
然后对dex文件进行优化,并保存在dalvik-cache目录下。
将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
安装完成后,发送广播。
WEEX
本质
新建WebViewManager继承自SimpleViewManager<BridgeWebView>
实现createViewInstance方法 返回一个webview实例
两种交互方式
1.通过injectedJavaScript 注入JS ,在H5页面加载之后立即执行。相当于webview端主动调用
2.Webview 和 H5 相互发送监听消息
Weex主要包括三大部分:JS Bridge、Render、Dom,分别对应WXBridgeManager、WXRenderManager、WXDomManager 。通过WXSDKManager统一管理。其中JS Bridge和Dom运行在独立的HandlerThread中,而Render运行在UI线程。JS Bridge主要用来和 JS 端实现进行双向通信,比如把js端的dom结构传递给Dom线程。Dom主要是用于负责dom的解析、映射、添加等等的操作,最后通知UI线程更新。而Render负责在UI线程中对dom实现渲染。
Weex 渲染流程
虚拟DOM.
构造树结构. 分析虚拟DOM JSON数据以构造渲染树(RT).
添加样式. 为渲染树的各个节点添加样式.
创建视图. 为渲染树各个节点创建Native视图.
绑定事件. 为Native视图绑定事件.
CSS布局. 使用 css-layout 来计算各个视图的布局.
更新视窗(Frame). 采用上一步的计算结果来更新视窗中各个视图的最终布局位置.
最终页面呈现.
skia
https://blog.csdn.net/jxt1234and2010/article/details/42572559
指令层:SkPicture、SkDeferredCanvas->SkCanvas
这一层决定需要执行哪些绘图操作,绘图操作的预变换矩阵,当前裁剪区域,绘图操作产生在哪些layer上,Layer的生成与合并。
2、解析层:SkBitmapDevice->SkDraw->SkScan、SkDraw1Glyph::Proc
这一层决定绘制方式,完成坐标变换,解析出需要绘制的形体(点/线/规整矩形)并做好抗锯齿处理,进行相关资源解析并设置好Shader。
3、渲染层:SkBlitter->SkBlitRow::Proc、SkShader::shadeSpan等
这一层进行采样(如果需要),产生实际的绘制效果,完成颜色格式适配,进行透明度混合和抖动处理(如果需要)
指令层与实现层分离
SkCanvas不直接执行渲染,由SkBaseDevice根据设备类型,选择渲染方法。这样虽然是同一套API,但可以用作GPU绘图、pdf绘制、存储显示列表等各种功能。在API集上做优化,避免冗余绘制,也因此成为可能(注:这个google虽然在尝试,但目前看来没有明显效果,实现起来确实也很困难)。
2、图=形+色的设计思想
由SkDraw和SkScan类中控制绘制的形,由SkBlitter和SkShader控制绘制的色,将绘图操作分解为形状与色彩两部分,这一点和OpenGL的顶点变换——光栅——片断着色管线相似,非常有利于扩展,各种2D图元的绘制基本上就完全支持了。
3、性能调优集中化
将耗时的函数抽象都抽象为proc,由一个工厂制造,便于集中对这一系列函数做优化。
图像处理
这种情况下,开发者自行基于目标Bitmap创建Canvas,调用Canvas的API绘制图像,一般是作图像的缩放、旋转处理,也可以加入渐变特效。(不是 lockCanvas 或 继承 onDraw 方法中传入的Canvas,就别想拿去上屏了)
3、图像编解码
Skia对各种类型的图片作了适配,提供统一的接口,开发者调用BitmapFactory,BitmapFactory进一步调用jni——skia。
(1)关于图像全解,这部分调用逻辑看上去简单,实际上对于输入输出流的处理还是比较复杂的,涉及Java的流——Skia规定的流——对应解码库的流两重转换。
(2)关于区域解码,这部分是google为平衡内存——性能——显示速度而设计的方案,一些Android机器上的图库打开照片时有一块一块渐渐清晰的过程,就是区域解码然后局部刷新的结果。
区域解码分成两步:
a、创建tileIndex,以便查找某个区域所对应的码流位置。
b、解码:输入指定区域,按照tileIndex查找对应码流,将对应区域的图片解出来,这个过程一般会调用多次。