Activity、IPC、View、drawable、动画、window、四大组件、消息机制、线程、bitmap、性能优化
Activity
- 生命周期
oncreate–onstart–onresume–onpause–onstop–ondestory onrestart
(对应的阶段是:完整生命周期–可见生命周期–前台生命周期)
(onSaveInstanceState和onRestoreInstanceState:生命周期异常情况下调用。
出现异常情况时,onSaveInstanceState来保存状态;Activity重建时onRestoreInstanceState恢复状态
避免旋转屏幕导致的重建,只需要设置属性android:configChanges)
(内存不足时,会按照优先级从低到高杀死antivity:前台>可见>后台) - 四种启动模式
设置方法:在AndroidManifest里配置,或者通过标记位设定,方法是intent.addFlags(Intent.xxx)
standard:(标准模式,默认模式):每次启动都会生成一个新的activity实例,进入所在在的栈(多次进入,那么需要多次返回才能退出)
singleTop:栈顶复用:如果新Activity已经位于任务栈的栈顶,就不会重新创建,并回调onNewIntent(intent)方法。
singletask:栈内复用:如果新Actity在一个栈中存在。那么就不会重建,并回调onNewIntent方法。如果不存在则创建任务栈并把activity放进去。
singleinstance:具有此模式的Activity只能单独位于一个任务栈中,且此任务栈中只有唯一一个实例。 - intentfilter匹配规则
匹配原则:一个intent只有同时匹配某个Activity的intent-filter中的action、category、data才算完全匹配,才能启动该Activity;一个Activity可以有多个 intent-filter,一个 intent只要成功匹配任意一组 intent-filter,就可以启动该Activity。
Action:要求intent中的action 存在且必须和intent-filter中的其中一个 action相同(区分大小写)。
category:系统有默认的属性。若有其他category,则要求intent中的category和intent-filter中的所有category 相同。
data:intent-filter中有定义data,那么Intent中也必须也要定义data。data主要由mimeType(媒体类型)和URI组成。在匹配时通过intent.setDataAndType(Uri data, String type)方法对date进行设置。
附:采用隐式方式启动Activity时,可以用PackageManager的resolveActivity方法或者Intent的resolveActivity方法判断是否有Activity匹配该隐式Intent。
IPC
- 概念
进程间通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法。
进程:一般指一个执行单元,在PC和移动设备上指一个程序或应用。
线程:CPU调度的最小单元。线程是一种有限的系统资源。
(一个进程可包含多个线程,即一个应用程序上可以同时执行多个任务。)
开启多进程的方式:
1、(不常用)通过JNI在native层fork一个新的进程。
2、(常用)在AndroidMenifest中给四大组件指定属性android:process
【以“:”开头的进程:属于当前应用的私有进程,其他进程的组件不能和他跑在同一进程中。】
【完整命名的进程:全局进程,其他应用可以通过ShareUID方式和他跑在用一个进程中。】
(UID&ShareUID:android系统为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。两个应用通过ShareUID跑在同一进程的条件:ShareUID相同且签名也相同。)
多进程造成的影响
1、静态变量和单例模式失效、线程同步机制失效、Application多次创建。(原因:由独立的虚拟机造成)
2、sharedpreferences不可靠。(不支持两个进程同时进行读写操作) - 序列化:序列化Serializable和Parcelable的理解和区别
- IPC
多进程使用场景
1、特殊模块需要运行在单独进程中
2、加大一个应用可使用的内存,通过多进程来获取多份内存空间
Android的进程架构:每一个Android进程都是独立的,且都由两部分组成,一部分是用户空间,另一部分是内核空间
Binder机制(一种跨进程通信方式)
binder作为android主要IPC方式,优点为:
1、传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。(而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程)
2、实现C/S架构方便:Binder基于C/S 架构 ,Server端与Client端相对独立,稳定性较好。
3、安全性高:Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。
Binder框架定义了四个角色:Server,Client,ServiceManager和Binder驱动。 - 其他IPC方式
其他六种IPC方式是通过Binder来实现的
1、Bundle:支持在Activity、Service和Receiver之间通过Intent.putExtra()传递Bundle数据。
通过Bundle在Android Activity间传递数据
Bundle实现Parcelable接口,它可方便的在不同的进程中传输。(通过intent或者bundle传递数据,其本质都是通过bundle传递)
2、文件共享:两个进程通过读/写同一个文件来交换数据。比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。
适用情况:对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。(SharedPreferences也是文件存储的一种,但不建议采用。因为系统对SharedPreferences的读/写有一定的缓存策略)
3、AIDL((Android Interface Definition Language,Android接口定义语言):如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。
AIDL的本质是系统提供了一套可快速实现Binder的工具。
4、Messager:轻量级的IPC方案,通过它可在不同进程中传递Message对象。
底层实现是AIDL,即对AIDL进行了封装,更便于进行进程间通信。
5、ContentProvider:Android提供的专门用来进行不同应用间数据共享的方式。
6、Socket:不仅可跨进程,还可以跨设备通信。 - Binder连接池
场景:有多个业务模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的aidl文件,那么相应的Service就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。
作用:将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service。
工作原理:每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象。服务端只需要一个Service,服务器提供一个queryBinder接口,它会根据业务模块的特征来返回相应的Binder对像,不同的业务模块拿到所需的Binder对象后就可进行远程方法的调用了。
view
- view事件体系
1、位置参数:坐标系
left、right、top、bottom是view的初始坐标(在绘制完毕后就不会再改变,均是相对于父view。)
x、y 是view偏移后的坐标,手指触摸屏幕所对应的位置信息。(x=left+translationX,且x、y是相对于父view。)(translationX是偏移量)
RawX、RawY是手指触摸屏幕所对应的 距离屏幕最左端的位置、最顶端的位置。
(Android应用坐标系统全面详解)
2、触控事件
MotionEvent:手指触摸屏幕产生的事件:ACTION_DOWN、ACTION_MOVE、ACTION_UP(一般事件指的是手指按下之后,产生一系列的滑动事件,然后离开屏幕的过程。)
【MotionEvent 对象可以得到触摸事件的x、y坐标。其中通过getX()、getY()可获取相对于当前view左上角的x、y坐标;通过getRawX()、getRawY()可获取相对于手机屏幕左上角的x,y坐标。】
TouchSlop:系统所能识别的被认为是滑动的最小距离(该常量和设备有关,可用它来判断用户的滑动是否达到阈值,获取方法:ViewConfiguration.get(getContext()).getScaledTouchSlop()。)
VelocityTracker:速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。
GestureDetector:手势检测,用于检测用户单击、滑动、长按、双击等行为。
3、滑动事件
View滑动1(不平滑):scrollTo/scrollBy。(scrollTo是绝对滑动,scrollBy是相对滑动。scrollBy调用了scrollTo)而且两者这是对view内容滑动,不对本身滑动
View滑动2(不平滑):通过动画:主要通过改变View的translationX和translationY参数来实现
View滑动3(平滑):通过改变View的LayoutParams使得View重新布局
弹性滑动1:Scroller(配合View的computerScroller方法配合使用。)
弹性滑动2:动画(弹性滑动)
弹性滑动2:延时策略。handler+view的postdelayed - 事件分发
本质:对点击事件MotionEvent事件分发的过程,系统将这个事件分配到具体的view上。顺序:activity–window–viewgroup–view(Activity、View、Window的理解)
关键的方法:dispatchTouchEvent(分发,boolea类型)–onInterceptTouchEvent(拦截,只在ViewGroup中有)–onTouchEvent(处理)
事件分发是逐级下发的,目的是将事件传递给具体的View。
ViewGroup一旦拦截事件,就不往下分发,同时调用onTouchEvent处理事件。
滑动冲突
产生原因:一般情况下,在一个界面里存在内外两层可同时滑动的情况时,会出现滑动冲突现象。
解决办法:从业务上出发,通过内外拦截,以及具体的滑动方向、速度来解决
外部拦截法:指点击事件都先经过父容器的拦截处理(父控件决定拦截不拦截)【需要重写父容器的onInterceptTouchEvent方法,在内部做出相应的拦截】
内部拦截法:子容器的拦截处理(子控件决定拦截不拦截)【父View需要重写onInterceptTouchEvent】 - view工作原理
view工作流程:meature(测量宽高)–layout(获取四个顶点位置)–draw(绘制到屏幕上)
viewroot的对应类viewrootImpl调用performTraversals方法,内部分别调用performMeasure–>performLayout–>performDraw,分别完成顶级 View(即decorView)的绘制。
meature:宽测量值widthMeasureSpec和高测量值heightMeasureSpec决定View的大小(32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize( 某种测量模式下的规格大小))(单个view通过meature测量,viewgroup测量自身后会通过遍历子元素去测量)
layout:确定View的最终宽高和四个顶点的位置
draw:绘制到屏幕(绘制背景–自己–children–装饰) - 自定义view
自定义之 继承view重写onDraw;
自定义之 继承viewGroup;
自定义之 继承特定view(如textview)
自定义之 继承特定的viewGroup(如linearlayout)
注意:
让view支持wrap_content、padding
不在view中使用handler,没有必要
view中如果有动画或线程,需及时停止。否则会内存泄露
如果有滑动嵌套,处理好滑动冲突
drawable
- 概念
Drawable是一种可在Canvas上进行绘制的对象,即可绘制物。
主要用途:作为ImageView中的图像显示,作为View的背景
创建方式:xml(常用)和java - 种类
BitmapDrawable表示一张图片(一般用于作为view的背景)
NinePatchDrawable(.9图,拉伸压缩后不失真)
ShapeDrawable(几何图形:圆矩形线条圆环等,一般用于背景)
StateListDrawable【常用】(Drawable的集合,每个Drawable对应着View的一种状态:比如点击、显示等(可实现淡入淡出效果))(探索Android中selector和shape的结合使用)
ClipDrawable(我们通过setLevel()方法设置其中的Drawable实例展示比例。可用展开画布样式 )
更多drawable:Android Drawable完全解析:Drawable子类用法总结 - 自定义drawable(通常没有必要自定义,因为不能在xml使用)
Drawable的工作原理的核心是draw()
创建自定义Drawable,必须重写其draw()、setAlpha()、setColorFilter()、getOpacity()等方法。
动画
如同要点提炼|开发艺术之Animation。
动画主要分 view动画、帧动画、属性动画。
- view动画分为失四种:平移动画、缩放动画、旋转动画和透明度动画。
(可以分别使用xml或java创建)
(可以给动画添加过程监听,这样在动画开始、结束和每一次循环时都可在回调方法中监听到。)
注意:View动画的View移动只是视觉效果,并不能真正的改变view的位置。
附加1:自定义view动画:继承Animation->重写initialize()和applyTransformation()方法:initialize()用于初始化;applyTransformation()用于进行矩阵变换.(实例:自定义补间动画、3D翻转动画)
附加2:View动画除了可作用在某个View对象上, 还可以用在特殊的场景,例如:控制ViewGroup的子View 的出场效果,还有Activity的切换效果。(Android动画总结——布局动画、转场动画) - 帧动画
(可以分别使用xml或java创建)
本质:按序播放一组预先定义好的图片
注意:使用祯动画要注意不能使用尺寸过大的图片,否则容易造成OOM( 内存溢出)错误。
应用场景:较为复杂的个性化动画效果。
(实例::Android 逐帧动画:关于 逐帧动画 的使用都在这里了!) - 属性动画
(在XML / Java代码中设置)
本质:在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。
概念:插值器(内置9种):动画变化的规律,趋势。 估值器(内置3种):辅助插值器计算的动画变化的具体的值
(ValueAnimator实现属性动画,是先改变值,然后手动赋值给对象的属性从而实现动画;是间接对对象属性进行操作;
ObjectAnimator实现属性动画,是先改变值,然后自动赋值给对象的属性从而实现动画;是直接对对象属性进行操作;)
附加:自定义插值器和估值器:自定义估值器需要实现 TypeEvaluator接口 & 复写evaluate();
附加:属性动画主要使用两个接口:AnimatorUpdateListener&AnimatorListener来监听动画的播放过程。
实例:你真的会使用插值器与估值器吗?
window
- 概念
window是一个抽象类,定义了顶级窗口的样式和行为,其唯一的实现类是PhoneWindow;
每个Window都对应一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系。Window并不可见,它实际以View的形式存在,它是View的直接管理者;
实际使用中无法访问Window,对Window的访问必须通过WindowManager,对Window的操作通过它完成。 - WindowManager的三个重要参数
flags:表示Window的属性:获得焦点?点击事件?显示等
type:表示Window的类型:系统window、子window、应用window
gravity:表示Window的位置。 - window的机制
增加修改删除(三个方法主要是定义在ViewManager接口中,接口WindowManager继承了ViewManager,WindowManagerImpl 实现了WindowManager,并通过WindowManagerGlobal进行及的操作)
【最终都会通过一个IPC过程移交给WindowManagerService。】【Window和View通过ViewRootImpl来联系,ViewRootImpl可控制View的测量、布局和重绘。】 - Window的创建过程
View必须依附Window才能呈现出来,因此有View的地方必有Window。在Android中可以提供View的地方有Activity、Dialog和Toast等。 - 附加
android界面:Window,PhoneWindow,DecorView,setContentView源码理解。activity–phonewindow–decorview–titleview contentview
四大组件
-
Activity
概念:展示界面并与用户交互 的组件。
需要在AndroidManifest中注册。
需要借助Intent启动:两种启动方式(显式、隐式)、四种启动模式(standard、singletop、singeinstance、singletask)
Activity的工作过程 -
Service
概念:计算型组件
需要在AndroidManifest中注册。
需要借助Intent启动:通过通过startService()启动,通过bindService()绑定。(通过unBindService()和stopService()完全停止一个Service。)
Service的工作过程 -
BroadcastReceiver
概念:消息型组件:在不同的组件乃至不同的应用之间传递消息。
两种注册方式:动态注册(需注册后才能接受广播)、静态注册(AndroidManifest里注册,无需启动应用即可注册)
需要借助Intent发送广播
四种类型:有序、无序、本地、粘性广播。
BroadcastReceiver的工作过程 -
ContentProvider
概念:共享型组件:向其他组件乃至其他应用共享数据。
需要在AndroidManifest中注册,无需intent启动。四种操作:增删查改。需手动停止。(注意处理好线程同步)
ContentProvider的工作过程
消息机制
- 作用
跨线程通信。(主线程进行UI操作,但不能进行耗时操作。子线程执行耗时操作,但不能操作UI。所以需要handler将在子线程耗时处理的结果通知给主线程进行UI操作)
实际使用:在主线程创建handler,重写handleMessage方法,对不同的message进行处理;在子线程持有handler的引用发送message即可。 - 要素
message:待处理的消息,包含了消息的id、待处理的对象等。由MessageQueue统一列队,最终由Handler处理。
handler:sendMessage:将消息发送到消息队列。handleMessage:处理相应的消息事件
MessageQueue:消息队列,存放handler发送来的消息,内部由单链表实现,等待looper抽取=
looper:Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。
(ThreadLocal:线程本地存储区,作用是帮助Handler获得当前线程的Looper) - 注意
管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。
如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
擅用message.what来标识信息,以便用不同方式处理message。
无论是主线程还是子线程,Looper只能被创建一次,即一个Thread只有一个Looper。
(子线程需自己创建looper。参考HandlerThread。)
附:android的消息处理机制(图+源码分析)——Looper,Handler,Message
线程
- 概念
cpu调度的最小单元。线程的创建和销毁是需要一定资源的,所以不能无限的产生。(避免频繁的创建线程造成的系统开销?使用线程池缓存一定量的线程来达到效果)
在android里分为主线程(即UI线程,主要处理界面交互相关)和子线程(主要执行耗时操作)。 - AsyncTask
轻量级的异步任务类。底层封装了线程池和Handler,便于执行后台任务以及在主线程中进行UI操作。
使用:新建类继承AsyncTask,重写方法。其中doInBackground方法是运行在子线程中的,用于处理耗时任务。
注意:必须在主线程种创建,只能执行一次。
注意:不适用于进行特别耗时的后台任务,而是建议用线程池 - HandlerThread
线程类,继承自Thread。内部有Looper。
用途:串行异步通信;构造intentService - IntentService
继承自Service的抽象类,内部封装了HandlerThread和Handler
优势属于服务,优先级比线程高;任务完成后,自动退出。
使用:继承IntentService,重写onHandleIntent方法;注册服务;在intent中启动。 - 线程池
重用线程池中的线程,避免线程的创建与销毁带来的性能损耗;有效控制线程池的最大并发数;进行线程管理。
线程池的概念来源:Java中的Executor,它是一个接口。
线程池的真正实现:ThreadPoolExecutor,提供一系列参数来配置线程池。
Bitmap&Cache
Bitmap类主要用于图片处理。(Bitmap(位图):指一张图片,常见格式:.png、.jpg等)
- Bitmap的高效加载
为什么要高效加载?直接加载大容量的高清Bitmap很容易出现显示不完整、内存溢出OOM的问题(如报错:
如何高效加载?计算一定的采样率将图片缩小后再加载进来。(使用BitmapFactory类) - 缓存
常用的缓存算法是LRU(Least Recently Used):最近最少使用算法
核心思想:当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。
两种方式:LruCache(内存缓存)、DiskLruCache(磁盘缓存)。
LruCache:内部采用一个LinkedHashMap以强引用。(LinkedHashMap利用一个双重链接链表来维护所有条目item) 使用方法:计算可用内存,分配LruCache缓存容量,重写sizeof方法,通过put()、get()和remove()实现数据的添加、获取和删除。
DiskLruCache:与LruCache区别:DiskLruCache非泛型类,不能添加类型,而是采用文件存储,存储和读取通过I/O流处理。 - imageLoader
内部封装了Bitmap的高效加载、LruCache和DiskLruCache
功能:同步加载、异步加载、图片压缩、内存缓存、磁盘缓存、网络拉取
场景:照片墙效果等
优化:不在adapter里直接加载图片、网络加载等;列表滑动时停止加载;开启硬件加速等。
性能优化
- 布局优化
思想:减少布局文件的层级
1、(尽量多使用线性布局和相对布局,不使用绝对布局)相同层级使用linearlayout,多嵌套情况下可使用RelativeLayout减少嵌套
2、使用< include >标签重用布局。
3、< merge >标签减少层级(通过include引入,自身不占用层级)
4、< ViewStub >标签懒加载。(ViewStub引入的布局不占用位置,在解析layout布局是节省了CPU和内存)
附:Android最佳性能实践–布局优化技巧 - 绘制优化
思想:避免在View.onDraw()中执行大量的操作。
1、避免创建新的局部对象。(多次调用ondraw可能会产生大量对象导致占用内存过多,系统频繁gc会降低执行效率)
2、避免做耗时任务、大量循环等 - 内存泄漏优化【是造成应用程序OOM的主要原因之一】
含义:当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
1、静态变量导致的内存泄漏?将内部类设为静态内部类独立出来。
2、单例模式导致内存泄露(单例传入参数this来自activity,使得持有activity的引用)?传参context.getApplicationContext()
3、资源未关闭导致的内存泄漏:Activity销毁的时候要及时关闭或者注销
无限循环的动画要及时停止;
BraodcastReceiver:调用unregisterReceiver()注销;
Cursor,Stream、File:调用close()关闭;
4、adapter:使用缓存的converview - 响应速度优化
思想:避免在主线程中做耗时操作
方式:使用多线程 - ListView优化
提高ListView性能的技巧
1、减少布局层级;
2、使用viewholder;
3、getview不要有太多的对象、不要有太多的逻辑操作;
4、滑动加载、不滑动不加载图片;
5、开启硬件加速
(ListView默认开启了animateCache,这会消耗大量的内存,因此会频繁调用GC,我们可以手动将它关闭掉) - Bitmap优化
(通过计算采样率)见bitmap篇。 - 线程优化
使用线程池避免大量线程。见线程篇。 - 其他
避免创建过多的对象 ;
不要过多使用枚举,枚举占用的内存空间要比整型大;
常量请使用static final来修饰;
使用一些Android特有的数据结构,比如ArrayMap、SparseArray和Pair等,它们都具有更好的性能;
适当使用软引用和弱引用;
采用内存缓存和磁盘缓存;
尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄露。