Activity是什么
Activity是四大组件之一,它提供一个界面让用户点击和各种滑动操作
Activity栈先进后出
四种状态:Running状态:一个新的Activity启动入栈后,它在屏幕最前端,处于栈的最顶端,此时它处于可见并可和用户交互的激活状态。
Paused状态:当Activity被另一个透明或者Dialog样式的Activity覆盖时的状态。此时它依然与窗口管理器保持连接,系统继续维护其内部状态,它仍然可见,但它已经失去了焦点,故不可与用户交互。
Stopped状态:当Activity不可见时,Activity处于Stopped状态。当Activity处于此状态时,一定要保存当前数据和当前的UI状态,否则一旦Activity退出或关闭时,当前的数据和UI状态就丢失了。
Killed状态:Activity被杀掉以后或者被启动以前,处于Killed状态。这是Activity已从Activity堆栈中移除,需要重新启动才可以显示和使用。
生命周期:onCreate(),onStart(),onResume(),onPause(),onStop(),onDestroy(),onRestart()
启动模式:standard 默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。
singleTop 可以有多个实例,但是不允许多个相同Activity叠加。
singleTask 只有一个实例。
singleinstance 只有一个实例,并且这个实例独立运行在一个task中
Context、Activity、Application之间有什么区别?
Activity和Application都是Context的子类。Context从字面上理解就是上下文的意思,在实际应用中它也确实是起到了管理上下文环境中各个参数和变量的总用,方便我们可以简单的访问到各种资源。虽然Activity和Application都是Context的子类,但是他们维护的生命周期不一样。前者维护一个Acitivity的生命周期,所以其对应的Context也只能访问该activity内的各种资源。后者则是维护一个Application的生命周期
进程的优先级 :前台进程,可见进程,服务进程,后台进程,空进程
Fragment
1、Fragment为什么被称为第五大组件
Fragment比Activity更节省内存,其切换模式也更加舒适,使用频率不低于四大组件,且有自己的生命周期,而且必须依附于Activity
2、Activity创建Fragment的方式
静态创建
动态创建
3、FragmentPageAdapter和FragmentPageStateAdapter的区别
FragmentPageAdapter在每次切换页面的的时候,是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响
FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的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栈顶页面
Service
1、Service是什么
Service是四大组件之一,它可以在后台执行长时间运行操作而没有用户界面的应用组件
2、Service和Thread的区别
Service是安卓中系统的组件,它运行在独立进程的主线程中,不可以执行耗时操作。Thread是程序执行的最小单元,分配CPU的基本单位,可以开启子线程执行耗时操作
Service在不同Activity中可以获取自身实例,可以方便的对Service进行操作。Thread在不同的Activity中难以获取自身实例,如果Activity被销毁,Thread实例就很难再获取得到
3、Service启动方式
startService
bindService
4、Service生命周期
startService
onCreate()
onStartCommand()
onDestroy()
bindService
onCreate()
onBind()
onUnbind()
onDestroy()
Broadcast Receiver
1、Broadcast Receiver是什么
Broadcast是四大组件之一,是一种广泛运用在应用程序之间传输信息的机制,通过发送Intent来传送我们的数据
2、Broadcast Receiver的使用场景
同一App具有多个进程的不同组件之间的消息通信
不同App之间的组件之间的消息通信
3、Broadcast Receiver的种类
普通广播
有序广播
本地广播
Sticky广播
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实现的
Handler
1、Handler是什么
Handler通过发送和处理Message和Runnable对象来关联相对应线程的MessageQueue
2、Handler使用方法
post(runnable)
sendMessage(message)
3、Handler工作原理
Android消息机制:Android规定了只允许UI线程修改Activity里的UI组件,在子线程中修改Activity里的UI组件,会导致UI操作的线程不安全,并报出错误。为了保证Android的UI操作是线程安全的,Android提供了Handler消息传递机制来解决这个问题
相关概念:
主线程(UI线程)
定义:当程序第一次启动时,Android会同时启动一条主线程(Main Thread)
作用:主线程主要负责处理与UI相关的事件
Message(消息)
定义:Handler接收和处理的消息对象(Bean对象)
作用:通信时相关信息的存放和传递
ThreadLocal
定义:线程内部的数据存储类
作用:负责存储和获取本线程的Looper
Message Queue(消息队列)
定义:采用单链表的数据结构来存储消息列表
作用:用来存放通过Handler发过来的Message,按照先进先出执行
Handler(处理者)
定义:Message的主要处理者
作用:负责发送Message到消息队列&处理Looper分派过来的Message
Looper(循环器)
定义:扮演Message Queue和Handler之间桥梁的角色
作用:
消息循环:循环取出Message Queue的Message
消息派发:将取出的Message交付给相应的Handler
关系:Looper中存放有MessageQueen,MessageQueen中又有很多Message,当我们的Handler发送消息的时候,会获取当前的Looper,并在当前的Looper的MessageQueen当中存放我们发送的消息,而我们的MessageQueen也会在Looper的带动下,一直循环的读取Message信息,并将Message信息发送给Handler,并执行HandlerMessage()方法
Looper初始化:
Looper的创建会关联一个MessageQueen的创建
Looper对象只能被创建一次
Looper对象创建后被存放在sThreadLocal中
Looper的循环过程:
取出Looper和MessageQueen
进入消息循环,有消息则分发出去
消息资源的回收
Looper中最为重要的方法:
Looper.prepareMainLooper():该方法是Looper对象的初始化
Looper.loop():该方法会循环取出Message Queue的Message,将取出的Message交付给相应的Handler(Looper的作用就体现在这里)
quit():quit会直接退出Looper
quitSafety():quitSafety只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后退出Looper
MessageQueen:在MessageQueen中会使用enqueueMessage()方法存储Message,若是在中间插入,则根据Message创建的时间进行插入,用MessageQueen里面的next()方法拿出
Message就是用来存储Message中各种信息的Bean对象
Handler发送消息:
第一种方式:sendMessage(Message msg) sendMessage()方法的处理方法就是执行handleMessage()空方法
第二种方式:post(Ruunable r) post()方法的处理方法就是将传进来的Runnable执行run()方法
4、Handler引起的内存泄漏
原因:非静态内部类持有外部类的匿名引用,导致Activity无法释放
解决:
Handler内部持有外部Activity的弱引用
Handler改为静态内部类
Handler.removeCallback()
请解释下Android通信机制中Message、Handler、MessageQueen、Looper的之间的关系?
首先,是这个MessageQueen,MessageQueen是一个消息队列,它可以存储Handler发送过来的消息,其内部提供了进队和出队的方法来管理这个消息队列,其出队和进队的原理是采用单链表的数据结构进行插入和删除的,即enqueueMessage()方法和next()方法。这里提到的Message,其实就是一个Bean对象,里面的属性用来记录Message的各种信息。
然后,是这个Looper,Looper是一个循环器,它可以循环的取出MessageQueen中的Message,其内部提供了Looper的初始化和循环出去Message的方法,即prepare()方法和loop()方法。在prepare()方法中,Looper会关联一个MessageQueen,而且将Looper存进一个ThreadLocal中,在loop()方法中,通过ThreadLocal取出Looper,使用MessageQueen的next()方法取出Message后,判断Message是否为空,如果是则Looper阻塞,如果不是,则通过dispatchMessage()方法分发该Message到Handler中,而Handler执行handlerMessage()方法,由于handlerMessage()方法是个空方法,这也是为什么需要在Handler中重写handlerMessage()方法的原因。这里要注意的是Looper只能在一个线程中只能存在一个。这里提到的ThreadLocal,其实就是一个对象,用来在不同线程中存放对应线程的Looper。
最后,是这个Handler,Handler是Looper和MessageQueen的桥梁,Handler内部提供了发送Message的一系列方法,最终会通过MessageQueen的enqueueMessage()方法将Message存进MessageQueen中。我们平时可以直接在主线程中使用Handler,那是因为在应用程序启动时,在入口的main方法中已经默认为我们创建好了Looper。
AsyncTask
AsyncTask是什么:
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并主线程中更新UI,通过AsyncTask可以更加方便执行后台任务以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池
参数:
Params:表示后台任务执行时的参数类型(对应例子中的URL),该参数会传给AysncTask的doInBackground()方法
Progress:表示后台任务的执行进度的参数类型(对应例子中的Integer),该参数会作为onProgressUpdate()方法的参数
Result:表示后台任务的返回结果的参数类型(对应例子中的Long),该参数会作为onPostExecute()方法的参数
常用的AsyncTask继承的方法
onPreExecute():异步任务开启之前回调,在主线程中执行
doInBackground():执行异步任务,在线程池中执行
onProgressUpdate():当doInBackground中调用publishProgress时回调,在主线程中执行
onPostExecute():在异步任务执行之后回调,在主线程中执行
onCancelled():在异步任务被取消时回调
主分支
首先,execute()方法,开启异步任务
接着,onPreExecute()方法,异步任务开启前
接着,doInBackground()方法,异步任务正在执行
最后,onPostExecute()方法,异步任务完成
次分支
onProgressUpdate()方法,异步任务更新UI
onCancelled()方法,异步任务取消
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()
HandlerThread
1、HandlerThread产生背景
当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞
2、HanlderThread的特点、
HandlerThread本质上是一个线程,继承自Thread
HandlerThread有自己的Looper对象,可以进行Looper循环,可以创建Handler
HandlerThread可以在Handler的handlerMessage中执行异步方法
HandlerThread优点是异步不会堵塞,减少对性能的消耗
HandlerThread缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低
HandlerThread与线程池不同,HandlerThread是一个串行队列,背后只有一个线程
IntentService
1、IntentService是什么
IntentService是继承自Service并处理异步请求的一个类,其内部采用HandlerThread和Handler实现的,在IntentService内有一个工作线程来处理耗时操作,其优先级比普通Service高。当任务完成后,IntentService会自动停止,而不需要手动调用stopSelf()。另外,可以多次启动IntentService,每个耗时操作都会以工作队列的方式在IntentService中onHandlerIntent()回调方法中执行,并且每次只会执行一个工作线程
2、IntentService使用方法
创建Service继承自IntentService
覆写构造方法和onHandlerIntent()方法
在onHandlerIntent()中执行耗时操作
Android事件分发机制
Android事件分发机制的发生在View与View之间或者ViewGroup与View之间具有镶嵌的视图上,而且视图上必须为点击可用。当一个点击事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,即事件先传递给Activity,再到Window,再到顶级View,才开始我们的事件分发
Android事件分发机制主要由三个重要的方法共同完成的
dispatchTouchEvent:用于进行点击事件的分发
onInterceptTouchEvent:用于进行点击事件的拦截
onTouchEvent:用于处理点击事件
需要注意的是View中是没有onInterceptTouchEvent()方法的
一、简要的谈谈Android的事件分发机制?
当点击事件发生时,首先Activity将TouchEvent传递给Window,再从Window传递给顶层View。TouchEvent会最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则交给这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false ,则交给这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“消失”,而且接收不到下一次事件。
二、为什么View有dispatchTouchEvent方法?
因为View可以注册很多事件的监听器,如长按、滑动、点击等,它也需要一个管理者来分发
三、ViewGroup中可能有很多个子View,如何判断应该分配给哪一个?
根据源码可知,它会分配给在点击范围内的子View
四、当点击时,子View重叠应该如何分配?
一般分配给最上层的子View,这是由于安卓的渲染机制导致的
视图工作机制
Android视图工作机制按顺序分为以下三步:
measure:确定View的宽高
layout:确定View的位置
draw:绘制出View的形状
相关概念:
View(照片框):自定义View
measure(尺子):测量View大小
MeasureSpec(尺子刻度):测量View大小的测量单位
layout(照片框的位置):View的具体位置
draw(笔):绘制View
(你会发现,现实中的画图步骤和View工作机制步骤是一样的)
Android视图工作机制之MeasureSpec
我们知道,自定义View第一步是测量,而测量需要测量规格(或测量标准)才能知道View的宽高,所以在测量之前需要认识MeasureSpec类
MeasureSpec类是决定View的measure过程的测量规格(比喻:尺子),它由以下两部分组成
SpecMode:测量模式(比喻:直尺、三角尺等不同类型)
SpecSize:测量模式下的规格大小(比喻:尺子的刻度)
MeasureSpec的表示形式是32位的int值
高2位(前面2位):表示测量模式,即SpecMode
低30位(后面30位):表示在测量模式下的测量规格大小,即SpecSize
(其实就是MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配)
SpecMode又分为三种模式:
UNSPECIFIED:父容器不对View有任何大小的限制,这种情况一般用于系统内部,表示一种测量状态
EXACTLY:父容器检测出View所需要的精确大小,这时候View的值就是SpecSize
AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值
LayoutParams有三种情况:MATCH_PARENT、WARP_CONTENT、xxxdp(精确大小)
结论:子View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定的
首先要知道LayoutParams有三种情况:MATCH_PARENT、WARP_CONTENT、100dp(精确大小)
只要子View的MeasureSpec被确定,那么就可以在measure过程中,测量出子View的宽高
通过例子来解释:
1.假如父容器LinearLayout的MeasureSpec:EXACTLY、AT_MOST的任意一种
子View的LayoutParams:精确大小(100dp)
也就是说:子View必须是指定大小,不管父容器载不载得下子View
所以返回子View的MeasureSpec:EXACTLY
所以返回子View测量出来的大小:子View自身精确大小
2.假如父容器LinearLayout的MeasureSpec:EXACTLY、AT_MOST的任意一种
子View的LayoutParams:MATCH_PARENT
也就是说:子View必须占满整个父容器,那么父容器多大,子View就多大
所以返回子View的MeasureSpec:跟父容器一致
所以返回子View测量出来的大小:父容器可用大小
3.假如父容器LinearLayout的MeasureSpec:EXACTLY、AT_MOST的任意一种
子View的LayoutParams:WARP_CONTENT
也就是说:子View必须自适应父容器,父容器不管多小,你都不能超过它,只能自适应的缩小
所以返回子View的MeasureSpec:AT_MOST(不能超过父容器本身)
所以返回子View测量出来的大小:父容器可用大小
还有第四种:父容器是UNSPECIFIED的时候,由于父容器不知道自己多大,而子View又采用MATCH_PARENT、WARP_CONTENT的时候,子View肯定也不知道自己多大,所以只有当子View采用EXACTLY的时候,才知道自己多大
measure过程 :ViewGroup源码中,提供了一个measureChildren的方法来遍历调用子View的measure方法,而各个子View再递归去执行这个过程
View的measure过程:View的源码中,由于measure方法是个final类型的,所以子类不能重写此方法,View的measure方法中,会调用自身的onMeasure方法(平时,自定义View重写这个方法,就是对自定义的View根据自己定的规则来确定测量大小),从onMeasure方法中,有getDefaultSize()、getSuggestedMinimumWidth()、getSuggestedMinimumHeight(),如果你自定义不重写onMeasure()方法的话,那么系统就会采用默认的测量模式来确定你的测量大小,即getDefaultSize(),返回specSize,即View测量后的大, getSuggestedMinimumWidth和getSuggestedMinimumHeight原理是一样的,如果View没有设置背景,那么View的宽度为mMinWidth,而mMinWidth对应的就是android:minWidth这个属性的值,如果这个属性不指定,那么mMinWidth默认为0;如果指定了背景,那么View的宽度就是max(mMinWidth, mBackground.getMinimumWidth()),getMinimumWidth是在Drawable类中的,它返回的是Drawable的原始宽度,如果没有Drawable,则返回0
如果是自定义View的话,就重写onMeasure方法,将其默认的测量方式改为我们自己规定的测量方式,最后获得我们的宽高
layout过程:
绘制背景:drawBackground(canvas)
绘制自己:if (!dirtyOpaque) onDraw(canvas)
绘制children:dispatchDraw(canvas)
绘制装饰:onDrawForeground(canvas)
(我们常常就是重写onDraw()方法来绘制我们的自定义View,否则是没有图像的,这点在源码中也是提供了onDraw()的空实现方法给我们去绘制图像)
视图工作机制中的重绘
invalidate()和requestLayout(),常用于View重绘和更新,其主要区别如下
invalidate方法只会执行onDraw方法
requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。
所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法
invalidate方法用于UI线程中重新绘制视图
postInvalidate方法用于非UI线程中重新绘制视图,省去使用handler
ListView
ListView是什么:
ListView是能将一个数据集合以动态滚动的方式展示到用户界面上的View
ListView的优化:
重用convertView
使用ViewHolder
图片三级缓存
监听滑动事件
少用透明View
开启硬件加速
Android项目构建
Androd Studio等IDE则对整个过程进行了一个打包,当我们在Run project的时候,底层的打包工具就会被调用,打包流程都会自动执行。然后我们只需要对构建文件按照自己的需求进行相应的配置,就可以构建出自己所需要的项目。
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
proguard是什么
ProGuard工具是用于压缩、优化和混淆我们的代码,其主作用是移除或混淆代码中无用类、字段、方法和属性
proguard技术功能:
压缩
优化
混淆
预检测
proguard工作原理:
将无用的字段或方法存入到EntryPoint中,将非EntryPoint的字段和方法进行替换
为什么要混淆:
由于Java是一门跨平台的解释性语言,其源代码被编译成class字节码来适应其他平台,而class文件包含了Java源代码信息,很容易被反编译
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回调尽量避免耗时操作
OOM面试题
1、什么是OOM
OOM指Out of memory(内存溢出),当前占用内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存限制就会抛出Out of memory异常
2、OOM相关概念
内存溢出:指程序在申请内存时,没有足够的空间供其使用
内存泄漏:指程序分配出去的内存不再使用,无法进行回收
内存抖动:指程序短时间内大量创建对象,然后回收的现象
3、解决OOM
Bitmap相关
图片压缩
加载缩略图
在滚动时不加载图片
回收Bitmap
使用inBitmap属性
捕获异常
其他相关
listview重用convertView、使用lru
避免onDraw方法执行对象的创建
谨慎使用多进程
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是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中
三级缓存:网络缓存,本地缓存,内存缓存
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线程中做耗时操作
内存泄漏
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()进行修复
内存管理
1、Android内存管理机制
分配机制
管理机制
2、内存管理机制的特点
更少的占用内存
在合适的时候,合理的释放系统资源
在系统内存紧张的时候,能释放掉大部分不重要的资源
能合理的在特殊生命周期中,保存或还原重要数据
3、内存优化方法
Service完成任务后应停止它,或用IntentService(因为可以自动停止服务)代替Service
在UI不可见的时候,释放其UI资源
在系统内存紧张的时候,尽可能多的释放非重要资源
避免滥用Bitmap导致内存浪费
避免使用依赖注入框架
使用针对内存优化过的数据容器
使用ZIP对齐的APK
使用多进程
冷启动和热启动面试题
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
其他优化
1、Android不用静态变量存储数据
静态变量等数据由于进程已经被杀死而被初始化
使用其他数据传输方式:文件/sp/contentProvider
2、SharePreference安全问题
不能跨进程同步
文件不宜过大
3、内存对象序列化
Serializeble:是java的序列化方式,Serializeble在序列化的时候会产生大量的临时对象,从而引起频繁的GC
Parcelable:是Android的序列化方式,且性能比Serializeble高,Parcelable不能使用在要将数据存储在硬盘上的情况
4、避免在UI线程中做繁重的操作
热更新
1、热更新主要流程
线上检查到Crash
拉出Bugfix分支修复Crash问题
jenkins构建和补丁生成
app通过推送或主动拉取补丁文件
将Bugfix代码合到master上
2、热更新主流框架
Dexposed
AndFix
Nuwa
3、热更新的原理
在ClassLoader创建一个dexElements数组
将修复好的dex文件存放在dexElements数组的最前面
ClassLoader会遍历dexElements数组,找到最前面的dex文件优先加载
进程保活
1、进程的优先级
空进程
后台进程
服务进程
可见进程
前台进程
2、Android进程回收策略
Low memory Killer(定时执行):通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为bad进程,杀死并释放内存
OOM_ODJ:判别进程的优先级
3、Android保活方案
利用系统广播拉活
利用系统Service机制拉活
利用Native进程拉活
利用JobScheduler机制拉活
利用账号同步机制拉活
常用第三方库
GsonFormat、Gson、fastjson快速搞定JSON数据解析
PullToRefresh下拉刷新和上拉加载
LeakCanary检测内存泄漏
RxJava2解锁图片三级缓存框架
Zxing实现二维码扫描
ShareSDk的使用,实现一键分享微信好友、朋友圈、QQ
Lint
1、什么是Android Lint
Android Lint是一个静态代码分析工具,它能够对你的Android项目中潜在的Bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查
2.配置Lint
创建Lint.xml到根目录下,自定义Lint安全等级等
在Java文件中可以使用@suppressLint(“NewApi”)来忽视Lint的报错
在xml文件中可以使用tool:ignore(“UnusedResources”)来忽视Lint的报错
自定义Lint检查,可以创建类,继承Detector和实现JavaPsiScanner
Kotlin
1、什么是Kotlin
Kotlin是一种基于JVM的编程语言
对Java的一种拓展,比Java更简洁
Kotlin支持函数式编程
Kotlin类和Java类可以相互调用