1、Activity 面试题
1、Activity 是什么
Activity 是四大组件之一,它提供一个界面让用户点击和各种滑动操作,这就是 Activity
2、Activity 四种状态
runing
paused
stopped
killed
3、Activity 生命周期
onCreate()
onStart()
onResume()
onPause()
onStop()
onDestroy()
onRestart()
4、进程的优先级
空进程
后台进程
服务进程
可见进程
前台进程
5、Activity 任务栈
先进后出
6、Activity 启动模式
standard
singletop
singletask: 能够保证 Activity A 在栈中只有一个实例,这个栈也允许存在其他的实例.
特别需要注意的是使用 startActivityForResult 方法的时候,根据 startActivityForResult 的 api 说明: if the activity you are launching uses the singleTask launch mode, it will not run in your task and thus you will immediately receive a cancel result
意思就是使用 startActivityForResult 方法启动一个 singleTask 模式的 Activity,该 Activity 不会在原来的任务栈中启动,因此会立即收到一个 RESULT_CANCELED 结果
singleinstance: singleInstance 要求 activity 的实例不仅只有一个,并且整个 task 中只有一个 activity 实例,而 singleTask 所在的栈中允许存在其他 activity 的实例。
singleInstance 的 activity 也具有上面的特性,此外,activity 设置了 singleInstance,那么无论被启动的 activity 有没有设置 singleInstance,都无法获取返回值
7、scheme 跳转协议
Android 中的 scheme 是一种页面内跳转协议,通过定义自己的 scheme 协议,可以跳转到 app 中的各个页面
服务器可以定制化告诉 app 跳转哪个页面
App 可以通过跳转到另一个 App 页面
可以通过 H5 页面跳转页面(DeepLink):
<a href="[scheme]://[host]/[path]?[query]">启动应用程序</a> |
<intent-filter> |
2、Fragment 面试题
1、Fragment 为什么被称为第五大组件
Fragment 比 Activity 更节省内存,其切换模式也更加舒适,使用频率不低于四大组件,且有自己的生命周期,而且必须依附于 Activity
2、Activity 创建 Fragment 的方式
静态创建:在 activity 布局文件里声明 fragment,
activity_main.xml 中声明:
动态创建:通过 java 代码将 fragment 添加到已存的 ViewGroup 中
fragment = new MyFragment();
fragmentStack.push(fragment);
transaction = manager.beginTransaction();
transaction.add(R.id.fragments, fragment);
transaction.commit();
3、FragmentPageAdapter 和 FragmentStatePagerAdapter 的区别
FragmentPageAdapter 在每次切换页面的的时候,是将 Fragment 进行分离,适合页面较少的 Fragment 使用以保存一些内存,对系统内存不会多大影响
FragmentPagerAdapter 中所有创建过的 Fragment 都缓存在 FragmentManager 中;页面切换,只是调用 detach,而不是 remove,所以只执行 onDestroyView,而不是 onDestroy。
- destroyItem 的时候,调用 detach 从页面上移除 fragment,内存中还有当前实例
FragmentStatePagerAdapter,使用 mFragments 在每次切换页面的时候,是将 Fragment 进行回收,适合页面较多的 Fragment 使用,这样就不会消耗更多的内存
FragmentStatePagerAdapter 缓存的个数是 (mOffscreenPageLimit * 2 + 1),而不是全部;页面切换,调用 remove 删除,且要缓存 State。
- destroyItem 的时候,将当前 fragment = null,直接销毁 fragment
4、Fragment 生命周期
onAttach()
onCreate()
onCreateView()
onActivityCreated()
onStart()
onResume()
onPause()
onStop()
onDestroyView()
onDestroy()
onDetach()
5、Fragment 的通信
Fragment 调用 Activity 中的方法:getActivity
Activity 调用 Fragment 中的方法:接口回调
Fragment 调用 Fragment 中的方法:FragmentManager.findFragmentById
6、Fragment 的 replace、add、remove 方法
replace:替代 Fragment 的栈顶页面
add:添加 Fragment 到栈顶页面
remove:移除 Fragment 栈顶页面
3、Service 面试题
1、Service 是什么
Service 是四大组件之一,它可以在后台执行长时间运行操作而没有用户界面的应用组件
2、Service 和 Thread 的区别
Service 是安卓中系统的组件,它运行在独立进程的主线程中,不可以执行耗时操作,超时会有 ANR 异常。Thread 是程序执行的最小单元,分配 CPU 的基本单位,可以开启子线程执行耗时操作
Service 在不同 Activity 中可以获取自身实例,可以方便的对 Service 进行操作。Thread 在不同的 Activity 中难以获取自身实例,如果 Activity 被销毁,Thread 实例就很难再获取得到
3、Service 启动方式
startService
bindService
4、Service 生命周期
startService:
onCreate()
onStartCommand(Intent intent, int flags, int startId)
onDestroy()
重点介绍下,onStartCommand() 方法,先看下官方 doc 里的介绍:
Added in API level 5
Called by the system every time a client explicitly starts the service by calling startService(Intent), providing the arguments it supplied and a unique integer token representing the start request. Do not call this method directly.
参数:
intent: The Intent supplied to startService(Intent), as given. This may be null if the service is being restarted after its process has gone away, and it had previously returned anything except START_STICKY_COMPATIBILITY.
flags: Additional data about this start request. Currently either 0, START_FLAG_REDELIVERY, or START_FLAG_RETRY.
startId: A unique integer representing this specific request to start. Use with stopSelfResult(int).
返回值:
The return value indicates what semantics the system should use for the service’s current started state. It may be one of the constants associated with the START_CONTINUATION_MASK bits.
总结如下,onStartCommand() 是当一个 service 调用 startService() 之后由系统调用的,开发者不用直接调用这个方法,但是可以重写这个方法。
方法的返回值有四个 int 值,分别是:START_STICKY, START_NOT_STICKY, START_REDELIVER_INTENT, or START_STICKY_COMPATIBILITY.
bindService:
onCreate()
onBind()
onUnbind()
onDestroy()
4、Broadcast Receiver 面试题
1、Broadcast Receiver 是什么
Broadcast 是四大组件之一,是一种广泛运用在应用程序之间传输信息的机制,通过发送 Intent 来传送我们的数据
2、Broadcast Receiver 的使用场景
同一 App 具有多个进程的不同组件之间的消息通信
不同 App 之间的组件之间的消息通信
3、Broadcast Receiver 的种类
普通广播
有序广播 Context.sendOrderedBroadcast
本地广播 LocalBroadcastManager.getInstance(context).sendBroadcast(intent) 除了能解决 BroadcastReceiver 进程间安全性问题外,相对 Context 操作的 BroadcastReceiver 而言还具有更高的运行效率。
Sticky 广播 的 sendStickyBroadcast(Intent) 接口发送,需要添加权限
也可以通过 Context 的 removeStickyBroadcast(Intent intent) 接口移除缓存的粘性广播。
sticky 有序广播 StickyOrderedBroadcast
4、Broadcast Receiver 的实现
静态注册:注册后一直运行,尽管 Activity、进程、App 被杀死还是可以接收到广播
动态注册:跟随 Activity 的生命周期
5、Broadcast Receiver 实现机制
自定义广播类继承 BroadcastReceiver,复写 onReceiver()
通过 Binder 机制向 AMS 进行注册广播
广播发送者通过 Binder 机制向 AMS 发送广播
AMS 查找符合相应条件的广播发送到 BroadcastReceiver 相应的循环队列中
消息队列执行拿到广播,回调 BroadcastReceiver 的 onReceiver()
6、LocalBroadcastManager 特点
本地广播只能在自身 App 内传播,不必担心泄漏隐私数据
本地广播不允许其他 App 对你的 App 发送该广播,不必担心安全漏洞被利用
本地广播比全局广播更高效
以上三点都是源于其内部是用 Handler 实现的
5、WebView 面试题
1、WebView 安全漏洞
API16 之前存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用 WebView.addJavascriptInterface 方法,远程攻击者可通过使用 Java 反射机制利用该漏洞执行任意 Java 对象的方法
2、WebView 销毁步骤
WebView 在其他容器上时(如:LinearLayout),当销毁 Activity 时,需要在 onDestroy() 中先移除容器上的 WebView,然后再将 WebView.destroy(),这样就不会导致内存泄漏
3、WebView 的 jsbridge
客户端和服务端之间可以通过 Javascript 来互相调用各自的方法
4、WebViewClient 的 onPageFinished
WebViewClient 的 onPageFinished 在每次完成页面的时候调用,但是遇到未加载完成的页面跳转其他页面时,就会一直调用,使用 WebChromeClient.onProgressChanged 可以替代
5、WebView 后台耗电
在 WebView 加载页面的时候,会自动开启线程去加载,如果不很好的关闭这些线程,就会导致电量消耗加大,可以采用暴力的方法,直接在 onDestroy 方法中 System.exit(0) 结束当前正在运行中的 java 虚拟机
6、WebView 硬件加速
Android3.0 引入硬件加速,默认会开启,WebView 在硬件加速的情况下滑动更加平滑,性能更加好,但是会出现白块或者页面闪烁的副作用,建议 WebView 暂时关闭硬件加速
7、WebView 内存泄漏
由于 WebView 是依附于 Activity 的,Activity 的生命周期和 WebView 启动的线程的生命周期是不一致的,这会导致 WebView 一直持有对这个 Activity 的引用而无法释放,解决方案如下
独立进程,简单暴力,不过可能涉及到进程间通信(推荐)
动态添加 WebView,对传入 WebView 中使用的 Context 使用弱引用
6、Binder 面试题
1、Linux 内核的基本知识
进程隔离 / 虚拟地址空间:进程间是不可以共享数据的,相当于被隔离,每个进程被分配到不同的虚拟地址中
系统调用:Linux 内核对应用有访问权限,用户只能在应用层通过系统调用,调用内核的某些程序
binder 驱动:它负责各个用户的进程,通过 binder 通信内核来进行交互的模块
而 Binder,就是充当 连接 两个进程(内核空间)的通道。
2、为什么使用 Binder
性能上,相比传统的 Socket 更加高效
安全性高,支持协议双方互相校验
虚线表示并非直接交互
说明 1:Client 进程、Server 进程 & Service Manager 进程之间的交互都必须通过 Binder 驱动(使用 open 和 ioctl 文件操作函数),而非直接交互
原因:
- Client 进程、Server 进程 & Service Manager 进程属于进程空间的用户空间,不可进行进程间交互
- Binder 驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互
说明 2: Binder 驱动 & Service Manager 进程 属于 Android 基础架构(即系统已经实现好了);而 Client 进程 和 Server 进程 属于 Android 应用层(需要开发者自己实现)
所以,在进行跨进程通信时,开发者只需自定义 Client & Server 进程 并 显式使用上述 3 个步骤,最终借助 Android 的基本架构功能就可完成进程间通信
详细参考这篇博客:图文详解 Android Binder 跨进程通信机制原理
对比 Linux (Android 基于 linux)上的其他进程通信方式(管道 / 消息队列 / 共享内存 / 信号量 / Socket),Binder 机制的优点有:
- 高效
Binder 数据拷贝只需要一次,而管道、消息队列、Socket 都需要 2 次
通过驱动在内核空间拷贝数据,不需要额外的同步处理 - 安全性高
Binder 机制为每个进程分配了 UID/PID 来作为鉴别身份的标示,并且在 Binder 通信时会根据 UID/PID 进行有效性检测
传统的进程通信方式对于通信双方的身份并没有做出严格的验证
如,Socket 通信 ip 地址是客户端手动填入,容易出现伪造 - 使用简单
采用 Client/Server 架构
实现 面向对象 的调用方式,即在使用 Binder 时就和调用一个本地对象实例一样
4、AIDL
客户端通过 aidl 文件的 Stub.asInterface() 方法,拿到 Proxy 代理类
通过调用 Proxy 代理类的方法,将参数进行封包后,调用底层的 transact() 方法
transact() 方法会回调 onTransact() 方法,进行参数的解封
在 onTransact() 方法中调用服务端对应的方法,并将结果返回
7、Handler 面试题
1、Handler 是什么
Handler 通过发送和处理 Message 和 Runnable 对象来关联相对应线程的 MessageQueue
2、Handler 使用方法
post(runnable)
sendMessage(message)
3、Handler 工作原理
Android 进阶——Android 消息机制之 Looper、Handler、MessageQueen
4、Handler 引起的内存泄漏
原因:非静态内部类持有外部类的匿名引用,导致 Activity 无法释放
解决:
Handler 内部持有外部 Activity 的弱引用
Handler 改为静态内部类
Handler.removeCallback()
8、AsyncTask 面试题
1、AsyncTask 是什么
它本质上就是一个封装了线程池和 Handler 的异步框架
2、AsyncTask 使用方法
三个参数
Params:表示后台任务执行时的参数类型,该参数会传给 AysncTask 的 doInBackground() 方法
Progress:表示后台任务的执行进度的参数类型,该参数会作为 onProgressUpdate() 方法的参数
Result:表示后台任务的返回结果的参数类型,该参数会作为 onPostExecute() 方法的参数
五个方法
onPreExecute():异步任务开启之前回调,在主线程中执行
doInBackground():执行异步任务,在线程池中执行
onProgressUpdate():当 doInBackground 中调用 publishProgress 时回调,在主线程中执行
onPostExecute():在异步任务执行之后回调,在主线程中执行
onCancelled():在异步任务被取消时回调
3、AsyncTask 工作原理
Android 进阶——多线程系列之异步任务 AsyncTask 的使用与源码分析
http://blog.csdn.net/qq_30379689/article/details/53203556
4、AsyncTask 引起的内存泄漏
原因:非静态内部类持有外部类的匿名引用,导致 Activity 无法释放
解决:
AsyncTask 内部持有外部 Activity 的弱引用
AsyncTask 改为静态内部类
AsyncTask.cancel()
5、AsyncTask 生命周期
在 Activity 销毁之前,取消 AsyncTask 的运行,以此来保证程序的稳定
6、AsyncTask 结果丢失
由于屏幕旋转、Activity 在内存紧张时被回收等情况下,Activity 会被重新创建,此时,旧的 AsyncTask 持有旧的 Activity 引用,这个时候会导致 AsyncTask 的 onPostExecute() 对 UI 更新无效
7、AsyncTask 并行 or 串行
AsyncTask 在 Android 2.3 之前默认采用并行执行任务,AsyncTask 在 Android 2.3 之后默认采用串行执行任务
如果需要在 Android 2.3 之后采用并行执行任务,可以调用 AsyncTask 的 executeOnExecutor()
8、一个 AsyncTask 对象只能被执行一次,即只能调用一次 execute,多次执行就报错。
同一个进程里所有用到 AsyncTask 的地方都是同一个线程池,导致任务缓存队列就容易满.
同一个进程里面所有 AsyncTask 对象都共享同一个 SerialExecutor 对象。
参考:https://blog.csdn.net/zhaoyangfang/article/details/76100607
在 Android2.3 中 AsyncTask 的调用可以并发执行. 在一个线程池中被调用. 该线程池有 5 个核心线程. 即一次可以并发执行 5 个任务
在 Android3.0 及其以后 AsyncTask 的调用在默认情况同时只能有 1 个任务在执行.
在 AsyncTask 中采用了 SerialExecutor, 线程池中只有一个线程的线程池但我们可以调用方法 asyncTask.executeOnExecutor(executor, params); 来设置自己的线程池实现并发。
9、HandlerThread 面试题
1、HandlerThread 产生背景
当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google 提供了 HandlerThread,HandlerThread 是在线程中创建一个 Looper 循环器,让 Looper 轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞
2、HanlderThread 的特点、
HandlerThread 本质上是一个线程,继承自 Thread
HandlerThread 有自己的 Looper 对象,可以进行 Looper 循环,可以创建 Handler
HandlerThread 可以在 Handler 的 handlerMessage 中执行异步方法
HandlerThread 优点是异步不会堵塞,减少对性能的消耗
HandlerThread 缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低
HandlerThread 与线程池不同,HandlerThread 是一个串行队列,背后只有一个线程。
Looper、Handler、Message 三者关系
转载自 Android 异步消息处理机制 让你深入理解 Looper、Handler、Message 三者关系
- 1、首先 Looper.prepare() 在本线程中保存一个 Looper 实例,然后该实例中保存一个 MessageQueue 对象;因为 Looper.prepare() 在一个线程中只能调用一次,所以 MessageQueue 在一个线程中只会存在一个。
- 2、Looper.loop() 会让当前线程进入一个无限循环,不端从 MessageQueue 的实例中读取消息,然后回调 msg.target.dispatchMessage(msg) 方法。
- 3、Handler 的构造方法,会首先得到当前线程中保存的 Looper 实例,进而与 Looper 实例中的 MessageQueue 想关联。
- 4、Handler 的 sendMessage 方法,会给 msg 的 target 赋值为 handler 自身,然后加入 MessageQueue 中。
- 5、在构造 Handler 实例时,我们会重写 handleMessage 方法,也就是 msg.target.dispatchMessage(msg) 最终调用的方法。
好了,总结完成,大家可能还会问,那么在 Activity 中,我们并没有显示的调用 Looper.prepare() 和 Looper.loop() 方法,为啥 Handler 可以成功创建呢,这是因为在 Activity 的启动代码中,已经在当前 UI 线程调用了 Looper.prepare() 和 Looper.loop() 方法。
10、IntentService 面试题
1、IntentService 是什么
IntentService 是继承自 Service 并处理异步请求的一个类,其内部采用 HandlerThread 和 Handler 实现的,在 IntentService 内有一个工作线程来处理耗时操作,其优先级比普通 Service 高。当任务完成后,IntentService 会自动停止,而不需要手动调用 stopSelf()。另外,可以多次启动 IntentService,每个耗时操作都会以工作队列的方式在 IntentService 中 onHandlerIntent() 回调方法中执行,并且每次只会执行一个工作线程
2、IntentService 使用方法
创建 Service 继承自 IntentService
覆写构造方法和 onHandlerIntent() 方法
在 onHandlerIntent() 中执行耗时操作
11、视图工作机制面试题
Android 进阶——Android 视图工作机制之 measure、layout、draw
Android 事件分发机制之 dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
12、ListView 面试题
1、ListView 是什么
ListView 是能将一个数据集合以动态滚动的方式展示到用户界面上的 View
2、ListView 的 RecycleBin 机制
3、ListView 的优化
重用 convertView
使用 ViewHolder
图片三级缓存
监听滑动事件
少用透明 View
开启硬件加速
13、Android 项目构建面试题
1、android 构建流程
2、jenkins 持续集成构建
这里可参考蒲公英文档
3、Git 常用命令
git init:仓库的初始化
git status:查看当前仓库的状态
git diff:查看仓库与上次修改的内容
git add:将文件放进暂存区
git commit:提交代码
git clone:克隆代码
git bransh:查看当前分支
git checkout:切换当前分支
4、git 工作流
fork/clone(主流)
fork:将别人的仓库代码 fork 到自己的仓库上
clone:克隆下自己仓库的代码
update、commit:修改代码并提交到自己的仓库
push:提交到自己的仓库
pull request:请求添加到别人的仓库
clone
5、proguard 是什么
ProGuard 工具是用于压缩、优化和混淆我们的代码,其主作用是移除或混淆代码中无用类、字段、方法和属性
6、proguard 技术功能
压缩
优化
混淆
预检测
7、proguard 工作原理
将无用的字段或方法存入到 EntryPoint 中,将非 EntryPoint 的字段和方法进行替换
8、为什么要混淆
由于 Java 是一门跨平台的解释性语言,其源代码被编译成 class 字节码来适应其他平台,而 class 文件包含了 Java 源代码信息,很容易被反编译
14、ANR 面试题
1、什么是 ANR
Application Not Responding,页面无响应的对话框
2、发生 ANR 的条件
应用程序的响应性是由 ActivityManager 和 WindowManager 系统服务监视的,当 ANR 发生条件满足时,就会弹出 ANR 的对话框
Activity 超过 5 秒无响应
BroadcastReceiver 超过 10 秒无响应
Service 超过 20 秒无响应
3、造成 ANR 的主要原因
主线程被 IO 操作阻塞
Activity 的所有生命周期回调都是执行在主线程的
Service 默认执行在主线程中
BoardcastReceiver 的回调 onReceive() 执行在主线程中
AsyncTask 的回调除了 doInBackground,其他都是在主线程中
没有使用子线程 Looper 的 Handler 的 handlerMessage,post(Runnable) 都是执行在主线程中
4、如何解决 ANR
使用 AsyncTask 处理耗时 IO 操作
使用 Thread 或 HandlerThread 提供优先级
使用 Handler 处理工作线程的耗时操作
Activity 的 onCreate 和 onResume 回调尽量避免耗时操作
15、OOM 面试题
1、什么是 OOM
OOM 指 Out of memory(内存溢出),当前占用内存加上我们申请的内存资源超过了 Dalvik 虚拟机的最大内存限制就会抛出 Out of memory 异常
2、OOM 相关概念
内存溢出:指程序在申请内存时,没有足够的空间供其使用
内存泄漏:指程序分配出去的内存不再使用,无法进行回收
内存抖动:指程序短时间内大量创建对象,然后回收的现象
3、解决 OOM
Bitmap 相关
图片压缩
加载缩略图
在滚动时不加载图片
回收 Bitmap
使用 inBitmap 属性
捕获异常
其他相关
listview 重用 convertView、使用 LruCache
避免 onDraw 方法执行对象的创建
谨慎使用多进程
16、Bitmap 面试题
1、recycle
在安卓 3.0 以前 Bitmap 是存放在堆中的,我们只要回收堆内存即可
在安卓 3.0 以后 Bitmap 是存放在内存中的,我们需要回收 native 层和 Java 层的内存
官方建议我们 3.0 以后使用 recycle 方法进行回收,该方法也可以不主动调用,因为垃圾回收器会自动收集不可用的 Bitmap 对象进行回收
recycle 方法会判断 Bitmap 在不可用的情况下,将发送指令到垃圾回收器,让其回收 native 层和 Java 层的内存,则 Bitmap 进入 dead 状态
recycle 方法是不可逆的,如果再次调用 getPixels() 等方法,则获取不到想要的结果
2、LruCache 原理
LruCache 是个泛型类,内部采用 LinkedHashMap 来实现缓存机制,它提供 get 方法和 put 方法来获取缓存和添加缓存,其最重要的方法 trimToSize 是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中
3、计算 inSampleSize
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { |
4、缩略图
public static Bitmap thumbnail(String path,int maxWidth, int maxHeight, boolean autoRotate) { |
5、保存 Bitmap
public static String save(Bitmap bitmap,Bitmap.CompressFormat format, int quality, File destFile) { |
6、保存到 SD 卡
public static String save(Bitmap bitmap,Bitmap.CompressFormat format, int quality, Context context) { |
7、三级缓存
网络缓存
本地缓存
内存缓存
下面介绍了 Android 位图内存管理的演变过程:
- 在 Android Android 2.2(API 级别 8)及更低版本上,当发生垃圾回收时,应用的线程会停止。这会导致延迟,从而降低性能。Android 2.3 添加了并发垃圾回收功能,这意味着系统不再引用位图后,很快就会回收内存。
- 在 Android 2.3.3(API 级别 10)及更低版本上,位图的后备像素数据存储在本地内存中。它与存储在 Dalvik 堆中的位图本身是分开的。本地内存中的像素数据并不以可预测的方式释放,可能会导致应用短暂超出其内存限制并崩溃。
- 从 Android 3.0(API 级别 11)到 Android 7.1(API 级别 25),像素数据会与关联的位图一起存储在
Dalvik 堆
上。 - 在 Android 8.0(API 级别 26)及更高版本中,位图像素数据存储在
原生堆
中。
更多关于 bitmap 位图数据存储的介绍,可以看下官方的介绍:https://developer.android.com/topic/performance/graphics/manage-memory
17、UI 卡顿面试题
1、UI 卡顿原理
View 的绘制帧数保持 60fps 是最佳,这要求每帧的绘制时间不超过 16ms(1000/60),如果安卓不能在 16ms 内完成界面的渲染,那么就会出现卡顿现象
2、UI 卡顿的原因分析
在 UI 线程中做轻微的耗时操作,导致 UI 线程卡顿
布局 Layout 过于复杂,无法在 16ms 内完成渲染
同一时间动画执行的次数过多,导致 CPU 和 GPU 负载过重
overDraw,导致像素在同一帧的时间内被绘制多次,使 CPU 和 GPU 负载过重
View 频繁的触发 measure、layout,导致 measure、layout 累计耗时过多和整个 View 频繁的重新渲染
频繁的触发 GC 操作导致线程暂停,会使得安卓系统在 16ms 内无法完成绘制
冗余资源及逻辑等导致加载和执行缓慢
ANR
3、UI 卡顿的优化
布局优化
使用 include、ViewStub、merge
不要出现过于嵌套和冗余的布局
使用自定义 View 取代复杂的 View
ListView 优化
复用 convertView
滑动不加载
背景和图片优化
缩略图
图片压缩
避免 ANR
不要在 UI 线程中做耗时操作
18、内存泄漏面试题
1、Java 内存泄漏引起的主要原因
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏
2、Java 内存分配策略
静态存储区:又称方法区,主要存储全局变量和静态变量,在整个程序运行期间都存在
栈区:方法体的局部变量会在栈区创建空间,并在方法执行结束后会自动释放变量的空间和内存
堆区:保存动态产生的数据,如:new 出来的对象和数组,在不使用的时候由 Java 回收器自动回收
3、Android 解决内存泄漏的例子
单例造成的内存泄漏:在单例中,使用 context.getApplicationContext() 作为单例的 context
匿名内部类造成的内存泄漏:由于非静态内部类持有匿名外部类的引用,必须将内部类设置为 static
Handler 造成的内存泄漏:使用 static 的 Handler 内部类,同时在实现内部类中持有 Context 的弱引用
避免使用 static 变量:由于 static 变量会跟 Activity 生命周期一致,当 Activity 退出后台被后台回收时,static 变量是不安全,所以也要管理好 static 变量的生命周期
资源未关闭造成的内存泄漏:比如 Socket、Broadcast、Cursor、Bitmap、ListView 等,使用完后要关闭
AsyncTask 造成的内存泄漏:由于非静态内部类持有匿名内部类的引用而造成内存泄漏,可以通过 AsyncTask 内部持有外部 Activity 的弱引用同时改为静态内部类或在 onDestroy() 中执行 AsyncTask.cancel() 进行修复
非静态内部类和非静态匿名内部类中确实都持有外部类的引用, 静态内部类中未持有外部类的引用
19、内存管理面试题
1、Android 内存管理机制
分配机制
管理机制
2、内存管理机制的特点
更少的占用内存
在合适的时候,合理的释放系统资源
在系统内存紧张的时候,能释放掉大部分不重要的资源
能合理的在特殊生命周期中,保存或还原重要数据
3、内存优化方法
Service 完成任务后应停止它,或用 IntentService(因为可以自动停止服务)代替 Service
在 UI 不可见的时候,释放其 UI 资源
在系统内存紧张的时候,尽可能多的释放非重要资源
避免滥用 Bitmap 导致内存浪费
避免使用依赖注入框架
使用针对内存优化过的数据容器
使用 ZIP 对齐的 APK
使用多进程
20、冷启动和热启动面试题
1、什么是冷启动和热启动
冷启动:在启动应用前,系统中没有该应用的任何进程信息
热启动:在启动应用时,在已有的进程上启动应用(用户使用返回键退出应用,然后马上又重新启动应用)
2、冷启动和热启动的区别
冷启动:创建 Application 后再创建和初始化 MainActivity
热启动:创建和初始化 MainActivity 即可
3、冷启动时间的计算
这个时间值从应用启动(创建进程)开始计算,到完成视图的第一次绘制为止
4、冷启动流程
Zygote 进程中 fork 创建出一个新的进程
创建和初始化 Application 类、创建 MainActivity
inflate 布局、当 onCreate/onStart/onResume 方法都走完
contentView 的 measure/layout/draw 显示在界面上
总结:Application 构造方法 ->attachBaseContext()->onCreate()->Activity 构造方法 ->onCreate()-> 配置主题中背景等属性 ->onStart()->onResume()-> 测量布局绘制显示在界面上
5、冷启动优化
减少第一个界面 onCreate() 方法的工作量
不要让 Application 参与业务的操作
不要在 Application 进行耗时操作
不要以静态变量的方式在 Application 中保存数据
减少布局的复杂性和深度
不要在 mainThread 中加载资源
通过懒加载方式初始化第三方 SDK
21、其他优化面试题
1、Android 不用静态变量存储数据
静态变量等数据由于进程已经被杀死而被初始化
使用其他数据传输方式:文件 / sp/contentProvider
2、SharePreference 安全问题
不能跨进程同步
文件不宜过大
3、内存对象序列化
Serializeble:是 java 的序列化方式,Serializeble 在序列化的时候会产生大量的临时对象,从而引起频繁的 GC
Parcelable:是 Android 的序列化方式,且性能比 Serializeble 高,Parcelable 不能使用在要将数据存储在硬盘上的情况
4、避免在 UI 线程中做繁重的操作
22、架构模式面试题
Android 基础——框架模式 MVC 在安卓中的实践
http://blog.csdn.net/qq_30379689/article/details/52909656
Android 基础——框架模式 MVP 在安卓中的实践
http://blog.csdn.net/qq_30379689/article/details/52910567
Android 基础——框架模式 MVVM 之 DataBinding 的实践
http://blog.csdn.net/qq_30379689/article/details/53037430
Android App 的设计架构:MVC,MVP,MVVM 与架构经验谈
https://www.tianmaying.com/tutorial/AndroidMVC
23、插件化面试题
1、插件化解决的问题
动态加载 APK(反射、类加载器)
资源加载(反射、AssetManager、独立资源、分段资源)
代码加载(反射获取生命周期)
2、类加载器(Java 中字节码添加到虚拟机中)
DexClassLoader:能够加载未安装的 jar/apk/dex,主要用于动态加载和代码热更新
PathClassLoader:只能加载系统中已经安装过的 apk
24、热更新面试题
1、热更新主要流程
线上检查到 Crash
拉出 Bugfix 分支修复 Crash 问题
jenkins 构建和补丁生成
app 通过推送或主动拉取补丁文件
将 Bugfix 代码合到 master 上
2、热更新主流框架
Dexposed
AndFix
Nuwa
Tinker
3、热更新的原理
在 ClassLoader 创建一个 dexElements 数组
将修复好的 dex 文件存放在 dexElements 数组的最前面
ClassLoader 会遍历 dexElements 数组,找到最前面的 dex 文件优先加载
25、进程保活面试题
1、进程的优先级
空进程
后台进程
服务进程
可见进程
前台进程
2、Android 进程回收策略
Low memory Killer(定时执行):通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为 bad 进程,杀死并释放内存
OOM_ODJ:判别进程的优先级
3、Android 保活方案
利用系统广播拉活
利用系统 Service 机制拉活
利用 Native 进程拉活
利用 JobScheduler 机制拉活
利用账号同步机制拉活
26、Lint 面试题
1、什么是 Android Lint
Android Lint 是一个静态代码分析工具,它能够对你的 Android 项目中潜在的 Bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查
3、配置 Lint
创建 Lint.xml 到根目录下,自定义 Lint 安全等级等
在 Java 文件中可以使用 @suppressLint(“NewApi”) 来忽视 Lint 的报错
在 xml 文件中可以使用 tool:ignore(“UnusedResources”) 来忽视 Lint 的报错
自定义 Lint 检查,可以创建类,继承 Detector 和实现 JavaPsiScanner
27、Kotlin 面试题
1、什么是 Kotlin
Kotlin 是一种基于 JVM 的编程语言
对 Java 的一种拓展,比 Java 更简洁
Kotlin 支持函数式编程
Kotlin 类和 Java 类可以相互调用
2、Kotlin 环境搭建
直接在 Plugin 中下载 Kotlin 插件即可
系统会自动配置到 Kotlin 环境
本文链接:http://agehua.github.io/2017/08/17/IntervieweeQuestions/