又到年末了,每到这个时候,咱们都会慢慢反思,这一年都作了什么?有什么进步?年初的计划都实现了吗?明年年初有跳槽的底气了吗?何况今年的互联网环境太差,须要本身有足够的知识储备,才可以应对这凌冽的寒风。java
本文主要是整理了中高级安卓须要会的(或者说面试被频繁问到的内容),主要做为参考大纲,以后会陆续更新每一个详细部分,供你们参考,互相学习。android
- BAT面试合集(Binder,组件化插件化,热修复,AOP,QQ换肤,虚拟机,https,线程池原理,音视频原理)
- 算法合集(Hash,KMP 等)
- 中小厂面试合集(内存泄漏,Handler,View,MVC.MVP.MVVM,)
- 大厂相关更新技术(Glide,数据库,NDK)
- 面试小知识(java小知识)
- 设计模式(设计模式原则和分类)
- 数据结构(数据结构等等)
- 网络编程(三次握手和四次握手,Volley,OKHttps,Retrofit)
- 源码解析(属性动画实现原理等)
- 多线程解析(线程同步,进程线程)
- 性能优化(Webview,内存泄漏和内存溢出等)
(顺手留下GitHub连接,须要获取相关面试或者面试宝典核心笔记PDF等内容的能够本身去找)
https://github.com/xiangjiana/Android-MSgit
将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。github
使用场景好比最多见的 AlertDialog
,拿咱们开发过程当中举例,好比 Camera 开发过程当中,可能须要设置一个初始化的相机配置,设置摄像头方向,闪光灯开闭,成像质量等等,这种场景下就可使用建造者模式面试
装饰者模式: 动态的给一个对象添加一些额外的职责,就增长功能来讲,装饰模式比生成子类更为灵活。装饰者模式能够在不改变原有类结构的状况下曾强类的功能,好比 Java 中的 BufferedInputStream 包装 FileInputStream,举个开发中的例子,好比在咱们现有网络框架上须要增长新的功能,那么再包装一层便可,装饰者模式解决了继承存在的一些问题,好比多层继承代码的臃肿,使代码逻辑更清晰算法
观察者模式,代理模式,门面模式,单例模式,生产者消费者模式sql
这个经过对比来描述,好比面向对象和面向过程的对比,针对这两种思想的对比,还能够举个开发中的例子,好比播放器的实现,面向过程的实现方式就是将播放视频的这个功能分解成多个过程,好比,加载视频地址,获取视频信息,初始化解码器,选择合适的解码器进行解码,读取解码后的帧进行视频格式转换和音频重采样,而后读取帧进行播放,这是一个完整的过程,这个过程当中不涉及类的概念,而面向对象最大的特色就是类,封装继承和多态是核心,一样的以播放器为例,一面向对象的方式来实现,将会针对每个功能封装出一个对象,吧如说Muxer,获取视频信息,Decoder,解码,格式转换器,视频播放器,音频播放器等,每个功能对应一个对象,由这个对象来完成对应的功能,而且遵循单一职责原则,一个对象只作它相关的事情数据库
1.继承 Thread 类实现多线程
2.实现 Runnable 接口
3.实现 Callable 接口
4.经过线程池编程
线程池的工做原理: 线程池能够减小建立和销毁线程的次数,从而减小系统资源的消耗,当一个任务提交到线程池时设计模式
a. 首先判断核心线程池中的线程是否已经满了,若是没满,则建立一个核心线程执行任务,不然进入下一步
b. 判断工做队列是否已满,没有满则加入工做队列,不然执行下一步
c. 判断线程数是否达到了最大值,若是不是,则建立非核心线程执行任务,不然执行饱和策略,默认抛出异常
Handler,Message,looper 和 MessageQueue
构成了安卓的消息机制,handler 建立后能够经过 sendMessage
将消息加入消息队列,而后 looper
不断的将消息从MessageQueue
中取出来,回调到 Hander
的handleMessage
方法,从而实现线程的通讯。
从两种状况来讲,第一在 UI 线程建立 Handler,此时咱们不须要手动开启 looper
,由于在应用启动时,在 ActivityThread
的 main 方法中就建立了一个当前主线程的looper
,并开启了消息队列,消息队列是一个无限循环,为何无限循环不会 ANR?由于能够说,应用的整个生命周期就是运行在这个消息循环中的,安卓是由事件驱动的,Looper.loop
不断的接收处理事件,每个点击触摸或者 Activity 每个生命周期都是在 Looper.loop
的控制之下的,looper.loop
一旦结束,应用程序的生命周期也就结束了。咱们能够想一想什么状况下会发生 ANR,第一,事件没有获得处理,第二,事件正在处理,可是没有及时完成,而对事件进行处理的就是looper,因此只能说事件的处理若是阻塞会致使 ANR,而不能说 looper 的无限循环会 ANR
另外一种状况就是在子线程建立Handler,此时因为这个线程中没有默认开启的消息队列,因此咱们须要手动调用 looper.prepare()
,并经过 looper.loop
开启消息主线程 Looper
从消息队列读取消息,当读完全部消息时,主线程阻塞。子线程往消息队列发送消息,而且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。所以 loop的循环并不会对 CPU 性能有过多的消耗。
非静态内部类会持有外部类的引用,若是非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类
匿名内部类一样会持有外部类的引用,若是在线程中执行耗时操做就有可能发生内存泄漏,致使外部类没法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务
Handler 致使的内存泄漏也能够被概括为非静态内部类致使的,Handler 内部
message 是被存储在 MessageQueue
中的,有些 message 不能立刻被处理,存在的时间会很长,致使 handler 没法被回收,若是 handler 是非静态的,就会致使它的外部类没法被回收,解决办法是 1.使用静态 handler,外部类引用使用弱引用处理 2.在退出页面时移除消息队列中的消息
根据场景肯定使用Activity的Context仍是Application的Context,由于两者生命周期不一样,对于没必要须使用 Activity 的 Context 的场景(Dialog),一概采用 Application
的 Context
,单例模式是最多见的发生此泄漏的场景,好比传入一个 Activity 的Context 被静态类引用,致使没法回收
使用静态 View 能够避免每次启动 Activity 都去读取并渲染 View,可是静态 View会持有 Activity 的引用,致使没法回收,解决办法是在 Activity 销毁的时候将静态View 设置为 null(View 一旦被加载到界面中将会持有一个 Context 对象的引用,在这个例子中,这个 context 对象是咱们的 Activity,声明一个静态变量引用这个View,也就引用了 activity)
WebView
只要使用一次,内存就不会被释放,因此 WebView
都存在内存泄漏的问题,一般的解决办法是为 WebView
单开一个进程,使用 AIDL
进行通讯,根据业务需求在合适的时机释放掉
如 Cursor,File 等,内部每每都使用了缓冲,会形成内存泄漏,必定要确保关闭它并将引用置为 null
集合用于保存对象,若是集合愈来愈大,不进行合理的清理,尤为是入股集合是静态的
bitmap 是比较占内存的,因此必定要在不使用的时候及时进行清理,避免静态变量持有大的 bitmap 对象
不少须要register和unregister
的系统服务要在合适的时候进行unregister
,手动添加的 listener 也须要及时移除
1.使用更加轻量的数据结构: 如使用 ArrayMap/SparseArray
替代HashMap
,HashMap
更耗内存,由于它须要额外的实例对象来记录 Mapping 操做,SparseArray
更加高效,由于它避免了 Key Value 的自动装箱,和装箱后的解箱操做
2.便面枚举的使用,能够用静态常量或者注解@IntDef
替代
3.Bitmap 优化:
a.尺寸压缩: 经过 InSampleSize 设置合适的缩放
b.颜色质量: 设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差别
c.inBitmap: 使用inBitmap
属性能够告知 Bitmap 解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用以前那张Bitmap在Heap中所占据的pixeldata
内存区域,而不是去问内存从新申请一块区域来存放 Bitmap。利用这种特性,即便是上千张的图片,也只会仅仅只须要占用屏幕所可以显示的图片数量的内存大小,但复用存在一些限制,具体体如今:在 Android 4.4 以前只能重用相同大小的 Bitmap 的内存,而 Android 4.4 及之后版本则只要后来的 Bitmap 比以前的小便可。使用inBitmap
参数前,每建立一个 Bitmap 对象都会分配一块内存供其使用,而使用了inBitmap
参数后,多个 Bitmap 能够复用一块内存,这样能够提升性能
4.StringBuilder 替代 String: 在有些时候,代码中会须要使用到大量的字符串拼接的操做,这种时候有必要考虑使用 StringBuilder
来替代频繁的“+”
5.避免在相似 onDraw
这样的方法中建立对象,由于它会迅速占用大量内存,引发频繁的 GC
甚至内存抖动
6.减小内存泄漏也是一种避免 OOM 的方法
a: Service 设置成 START_STICKY kill 后会被重启(等待 5 秒左右),重传 Intent,保持与重启前同样
b: 经过 startForeground 将进程设置为前台进程, 作前台服务,优先级和前台应用一个级别,除非在系统内存很是缺,不然此进程不会被 kill
c: 双进程 Service: 让 2 个进程互相保护对方,其中一个 Service 被清理后,另外没被清理的进程能够当即重启进程
d: 用 C 编写守护进程(即子进程) : Android 系统中当前进程(Process)fork 出来的子进程,被系统认为是两个不一样的进程。当父进程被杀死的时候,子进程仍然能够存活,并不受影响(Android5.0 以上的版本不可行)联系厂商,加入白名单
e. 锁屏状态下,开启一个一像素 Activity
app 冷启动: 当应用启动时,后台没有该应用的进程,这时系统会从新建立一个新的进程分配给该应用, 这个启动方式就叫作冷启动(后台不存在该应用进程)。冷启动由于系统会从新建立一个新的进程分配给它,因此会先建立和初始化 Application 类,再建立和初始化 MainActivity
类(包括一系列的测量、布局、绘制),最后显示在界面上。
app 热启动: 当应用已经被打开, 可是被按下返回键、Home 键等按键时回到桌面或者是其余程序的时候,再从新打开该 app 时, 这个方式叫作热启动(后台已经存在该应用进程)。热启动由于会从已有的进程中来启动,因此热启动就不会走 Application 这步了,而是直接走 MainActivity
(包括一系列的测量、布局、绘制),因此热启动的过程只须要建立和初始化一个 MainActivity
就好了,而没必要建立和初始化Application
当点击 app 的启动图标时,安卓系统会从 Zygote 进程中 fork 建立出一个新的进程分配给该应用,以后会依次建立和初始化 Application 类、建立MainActivity
类、加载主题样式 Theme 中的 windowBackground
等属性设置给 MainActivity
以及配置 Activity 层级上的一些属性、再 inflate 布局、当 onCreate/onStart/onResume
方法都走完了后最后才进行 contentView
的 measure/layout/draw
显示在界面上
Application
构造方法 –> attachBaseContext()
–>onCreate
–>Activity 构造方法 –>onCreate()
–> 配置主体中的背景等操做 –>onStart()
–> onResume()
–> 测量、布局、绘制显示
冷启动的优化主要是视觉上的优化,解决白屏问题,提升用户体验,因此经过上面冷启动的过程。能作的优化以下:
一、减小 onCreate()方法的工做量
二、不要让 Application 参与业务的操做
三、不要在 Application 进行耗时操做
四、不要以静态变量的方式在 Application 保存数据
五、减小布局的复杂度和层级
六、减小主线程耗时
缘由在于加载主题样式 Theme 中的 windowBackground
等属性设置给MainActivity
发生在 inflate 布局当 onCreate/onStart/onResume
方法以前,而windowBackground
背景被设置成了白色或者黑色,因此咱们进入 app 的第一个界面的时候会形成先白屏或黑屏一下再进入界面。解决思路以下
1. 给他设置 windowBackground
背景跟启动页的背景相同,若是你的启动页是张图片那么能够直接给 windowBackground
这个属性设置该图片那么就不会有一闪的效果了
<style name=``"Splash_Theme"` parent=``"@android:style/Theme.NoTitleBar"``>` <item name=``"android:windowBackground"``>@drawable/splash_bg</item>` <item name=``"android:windowNoTitle"``>``true``</item>`</style> `
2. 采用世面的处理方法,设置背景是透明的,给人一种延迟启动的感受。,将背景颜色设置为透明色,这样当用户点击桌面 APP 图片的时候,并不会"当即"进入APP,并且在桌面上停留一会,其实这时候 APP 已是启动的了,只是咱们心机的把 Theme 里的 windowBackground
的颜色设置成透明的,强行把锅甩给了手机应用厂商(手机反应太慢了啦)
<style name=``"Splash_Theme"` parent=``"@android:style/Theme.NoTitleBar"``>` <item name=``"android:windowIsTranslucent"``>``true``</item>` <item name=``"android:windowNoTitle"``>``true``</item>`</style> `
3. 以上两种方法是在视觉上显得更快,但其实只是一种表象,让应用启动的更快,有一种思路,将 Application
中的没必要要的初始化动做实现懒加载,好比,在SpashActivity
显示后再发送消息到 Application,去初始化,这样能够将初始化的动做放在后边,缩短应用启动到用户看到界面的时间
AsyncTask
,HandlerThread
,IntentService
AsyncTask
原理: 内部是 Handler 和两个线程池实现的,Handler 用于将线程切换到主线程,两个线程池一个用于任务的排队,一个用于执行任务,当 AsyncTask
执行 execute 方法时会封装出一个 FutureTask
对象,将这个对象加入队列中,若是此时没有正在执行的任务,就执行它,执行完成以后继续执行队列中下一个任务,执行完成经过 Handler 将事件发送到主线程。AsyncTask
必须在主线程初始化,由于内部的 Handler 是一个静态对象,在 AsyncTask
类加载的时候他就已经被初始化了。在 Android3.0 开始,execute 方法串行执行任务的,一个一个来,3.0 以前是并行执行的。若是要在 3.0 上执行并行任务,能够调用executeOnExecutor 方法
HandlerThread
原理: 继承自 Thread,start 开启线程后,会在其 run 方法中会经过 Looper
建立消息队列并开启消息循环,这个消息队列运行在子线程中,因此能够将 HandlerThread
中的 Looper
实例传递给一个 Handler,从而保证这个Handler 的 handleMessage
方法运行在子线程中,Android 中使用 HandlerThread
的一个场景就是 IntentService
IntentService
原理: 继承自 Service,它的内部封装了 HandlerThread
和 Handler,能够执行耗时任务,同时由于它是一个服务,优先级比普通线程高不少,因此更适合执行一些高优先级的后台任务,HandlerThread
底层经过 Looper
消息队列实现的,因此它是顺序的执行每个任务。能够经过 Intent 的方式开启IntentService
,IntentService
经过 handler 将每个 intent 加入 HandlerThread
子线程中的消息队列,经过 looper 按顺序一个个的取出并执行,执行完成后自动结束本身,不须要开发者手动关闭
1.耗时的网络访问
2.大量的数据读写
3.数据库操做
4.硬件操做(好比 camera)
5.调用 thread 的 join()方法、sleep()方法、wait()方法或者等待线程锁的时候
6.service binder 的数量达到上限
7.system server 中发生 WatchDog ANR
8.service 忙致使超时无响应
9.其余线程持有锁,致使主线程等待超时
10.其它线程终止或崩溃致使主线程一直等待
当 Android 端须要得到数据时好比获取网络中的图片,首先从内存中查(按键查找),内存中没有的再从磁盘文件或 sqlite
中去查找,若磁盘中也没有才经过网络获取
LruCache
中 Lru
算法的实现就是经过 LinkedHashMap
来实现的。LinkedHashMap
继承于HashMap
,它使用了一个双向链表来存储 Map 中的 Entry 顺序关系,对于 get、put、remove 等操做,LinkedHashMap
除了要作 HashMap
作的事情,还作些调整 Entry 顺序链表的工做。
LruCache
中将 LinkedHashMap
的顺序设置为 LRU 顺序来实现 LRU 缓存,每次调用 get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用 put 插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
JavaVM 是虚拟机在 JNI 层的表明,一个进程只有一个 JavaVM,全部的线程共用一个 JavaVM
。
JNIEnv
表示 Java 调用 native 语言的环境,是一个封装了几乎所有 JNI 方法的指针。JNIEnv
只在建立它的线程生效,不能跨线程传递,不一样线程的 JNIEnv
彼此独立。native 环境中建立的线程,若是须要访问 JNI,必需要调用 AttachCurrentThread
关联,并使用 DetachCurrentThread
解除连接。
方法: 对象继承 Serializable 类便可实现序列化,就是这么简单,也是它最吸引咱们的地方
Parcelable
方式的实现原理是将一个完整的对象进行分解,用起来比较麻烦
1)在使用内存的时候,Parcelable 比 Serializable 性能高,因此推荐使用 Parcelable。
2)Serializable 在序列化的时候会产生大量的临时变量,从而引发频繁的 GC。
3)Parcelable
不能使用在要将数据存储在磁盘上的状况,由于 Parcelable
不能很好的保证数据的持续性,在外界有变化的状况下。尽管 Serializable
效率低点,但此时仍是建议使用 Serializable
。
4)android 上应该尽可能采用 Parcelable
,效率至上,效率远高于 Serializable
(顺手留下GitHub连接,须要获取相关面试等内容的能够本身去找)
https://github.com/xiangjiana/Android-MS