20.《android开发艺术探索》笔记

一、Activity的生命周期和启动模式
1.Activity的生命周期全面分析
典型情况下的生命周期:onCreate,onStart,onResume,onPause,onStop,onRestart,onDestroy
Activity启动过程:由Instrumentatioin来处理,然后它通过Binder向AMS发请求,AMS内部维护着一个ActivityStack并负责栈内的Activity的状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用。
异常状况:onSaveInstanceState-->onDestroy ==> onCreate-->onRestoreInstanceState
系统只在Activity异常终止的时候才调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,其他情况不会触发这个过程。
资源内存不足时导致低优先级的Activity被杀死:前台Activity > 可见但非前台Activity > 后台Activity。
如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死。因此,后台工作放入Service中。
系统配置发生改变不重新创建Activity: android:configChanges = "orientation | keyboardHidden".

2.Activity的启动模式
启动模式:standard,singleTop,singleTask,singleInstance
Flags:FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_SINGLE_TOP,FLAG_ACTIVITY_CLEAR_TOP,FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

3,IntentFilter的匹配规则
action的匹配规则:要求Intent中的action存在,且必须和过滤规则中的其中一个action相同。
category的匹配规则:Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同。
data的匹配规则:Intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的其中一个data.

二、IPC机制
1.Android IPC简介
Windows:剪贴板、管道、邮槽
Linux:命名管道、共享内存、信号量
Android:Binder、Socket
需要多进程:横款需要运行在单独进程中、加大应用内存、向其他应用获取数据

2.多进程模式
开启:给四大组件指定android:process,非常规:通过JNI在native层去fork一个新的进程
进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一进程中,而进程名不以“:”开头的进程属于全局进程,其父应用通过ShareUID方式和它跑在同一进程中。
每个进程都被分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的空间,在不同的虚拟机访问同一个类的对象会产生多个副本。
多进程造成问题:静态成员和单例模式失效,线程同步机制完全失效,SharedPreferences的可靠性下降,Application会多次创建
跨进程通信实现方式:通过Intent来传递数据、共享文件和SharedPreferences、基于Binder和Messenger和AIDL、Socket

3.IPC基础概念介绍
Serializable接口:private static final long serialVersionUID:87113879893794827L,如不声明,会对反序列化造成影响
Parcelable接口:describeContents()、writeToParcel()、CREATOR

Binder:Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解成一种虚拟的物理设备,从Android Framework角度来说,是ServiceManager连接各种Manger和相应MangerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介。
DESCRIPTOR:Binder的唯一标识。
asInterface(android.os.IBinder obj):将服务端的Binder对象转换成客户端的AIDL接口类型对象。
asBinder:返回当前Binder对象
onTransact:运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理。服务端通过code确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数,然后执行。完毕后向reply中写入返回值。
Proxy#getBookList:运行在客户端,当客户端远程调用此方法时,首先创建该方法所需的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中(如果有参数 的话);接着调用transact方法中来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransace方法会被调用,直到RPC过程返回后,当前线程继续招行,并从_reply中取出PRC过程的返回结果,最后返回_reply中的数据。

4.Android中的IPC方式
使用Bundle:通过Intent发送,传输数据必须 被序列化,比如基本数据类型、实现Parcel接口的对象
通过文件共享:两个进程读/写同一个文件

使用Messenger:轻量级的IPC方法,底层是AIDL
服务端:在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象的Binder。
客户端进程:首先绑定服务端的Service,绑定成功后用服务器返回的Binder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发送消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。

使用AIDL:适合大量并发情况
服务端:首先创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Sercie中实现这个AIDL接口即可。
客户端:绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

使用ContentProvider:底层同样是Binder
使用Socket

5.Binder连接池:当需要使用多个AIDL时。

6.选用合适的IPC方式

三、View的事件体系
1.View基础知识
View是所有控件的基类。
View的位置主要由它的四个顶点来决定:top、left、right、bottom,对应左上纵坐标、左上横坐标、右下横坐标、右下纵坐标,是相对父容器来说的。x、y、translationX和translationY,x和y是左上角的坐标,后两者是左上方相对父容器的偏移量。
MotionEvent:ACTION_DOWN、ACTIONI_MOVE、ACTION_UP,getX/getY和getRawX/getRawY得到点击事件坐标
TouchSlop:滑动最小距离
VelocityTracker:速度追踪
GestureDetector:手势检测
Scroller:弹性滑动对象

2.View的滑动
使用ScrollTo/ScrollBy
使用动画:ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();
改变布局参数(LayoutParams)

3.弹性滑动
使用Scroller:startScroll方法下的invalidate方法实现的。Scroller本身并不能实现View的滑动,它需要配合View的computerScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘滑动起始时间会有一个间隔,通过这个时间间隔Scroller就可以得出View当前的滑动位置,知道了滑动位置就可以通过ScrollTo方法来完成View的滑动。
通过动画
使用延时策略:通过发送一系列延时消息从而达到一种渐近式的效果。可使用Hander或View的postDelay、线程的sleep方法。

4.View的事件分发机制
点击事件的传递规则:
dispatchEvent:用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dipatchTouchEvent方法的影响,表示是否消耗当前事件。
onInterceptTouchEvent:在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
onTouchEvent:在dispatchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
过程伪码表示如下:
public boolean dispatchTouchEvent (MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}

点击事件传递过程顺序:Activity --> Window --> View,如果没有View处理,最后返回给Activity
Activity对点击事件的分发过程:首先事件开始交给Activity的属性的Windwo进行分发,如果返回true,整个事件循环就结束了,返回false就意味着事件没人处理,所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用。
Window:PhoneWindow将事件直接传递给了DecorView。
顶级View对点击事件的分发过程:点击事件到达顶级View(一般是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后:如果顶级ViewGroup拦截事件即onInterceptTouchEvent返回true,则事件由ViewGroup处理,这时如果ViewGroup的onTouchListener被设置,则ouTouch会被调用,否则onTouchEvent会被调用。在onTouchEvent中,如果设置了onClickListener,则onClick会被调用。如果顶级ViewGroup不拦截,则事件传递给它所在事件链上的子View。
View(不包含ViewGroup):首先判断有没有设置onTouchListener,如果onTouchListener中onTouch返回true,那么onTouchEvent就不会被调用。

5.View的滑动冲突
常见场景:外部滑动方向和内部滑动方向不一致、外部滑动方向和内部滑动方向一致、前面两种情况的嵌套。
场景1:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,让内部View拦截点击事件,这个时候根据它们的特征来解决滑动冲突:根据滑动是水平滑动还是竖直滑动来判断到底由谁来拦截。

场景2、3:从业务需求上得出相应的处理规则。
外部拦截法:点击事件都先经过父容器的拦截处理,如果父容器需要些事件就拦截,如果不需要就不拦截。
内部拦截法:父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。

四、View的工作原理
1.初识ViewRoot和DecorView
ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot完成的。
View的绘制是从ViewRoot的performTraversals方法开始的,它经过measure、layout和在draw三个过程才能最终将一个View绘制出来,其中measure用来测量View的宽高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。
DecorView作为顶级View,一般情况下它内部会包含一个竖直方向的LinearLayout,上面是标题栏,下面是内容栏。Decor其实是一个FrameLayout。View层的事件都先经过DecorView,然后传递给View。

2.理解MeasureSpec
MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽高。
UNSPECIFIED:父容器不对View有任何限制,要多大给多大。
EXACTLY:父容器已经检测出View所需要的精确大小,这个时候view的最终大小就是specSize所指定的值,它对应于LayoutParams中的match_parent和具体数值这两种模式。
AT_MOST:父容器指定了一个可用大小即SpecSize,view的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。
LayoutParams和父容器一起决定View的MeasureSpec,从而进一步决定View的宽高。对于DecorView,其Measure由窗口尺寸和其自身的LayoutParams来共同确定,对于普通的view,其measureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,Measurespec一旦确定后,onMeasure中就可以确定View的测量宽高。

3.View的工作流程
measure:如果是一个原始的View,那么通过measure方法就完成了其测量过程;如果是一个viewgroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。
获取宽高:Activity/View # onWindowFocusChanged、view.post(runable)、ViewTreeObserver
、View.measure
Layout:Layout方法确定view本身的位置,onLayout方法确定所有子元素的位置。
Draw:将View绘制到屏幕上,绘制背景background.draw(canvas)、绘制自己(onDraw)、绘制child(dispatchDraw)、绘制装饰(onDrawScros)。

4.自定义view
自定义View须知:让View支持wrap_content;如果有必要,让你的View支持padding;尽量不要在View中使用Handler,没必要;View中如果有线程或者动画,需要及时停止;View带有滑动嵌套情形时,需要处理好滑动冲突。

五、理解RomoteViews
1.Remoteviews的应用
RemoteViews主要用在通知栏和桌面小部件的开发中。

2.RemoteViews的内部机制
通知栏和桌面小部件中的布局文件实际上是在NotificationManagerService以及AppWidgetService中被加载的,而它们运行在系统的SystemServer中。
首先RemoteViews会通过Binder传递到SystemServer进程,然后会通过LayoutInflater去加载RemoteViews中的布局文件,接着系统会对View执行一系列界面更新任务。
系统首先将View操作封装到Action对象,并将这些对象跨进程传输到这个远程进程,接着在远程进程中执行Action对象中的具体操作。

3.RemoteViews的意义
当一个应用需要能够更新另一个应用中的某个界面,用RemoteViews比AIDL更有优势

七、Android中的Drawable
1.Drawable简介
Drawable有很多种,它们都表示一种图像的概念,但是它们又不全是图片,通过颜色也可以构造出各式各样的图像效果。
Drawable的内部宽高这个参数比较重要,通过getIntinsicWidth和getIntinsicHeight这两个方法可以获取到它们。但并不是所有的Drawable都有内部宽高,而且内部宽高不等同于它们的大小,一般来说Drawable是没有大小概念的。

2.Drawable的分类
BitmapDrawable(<bitmap>):表示一张图片。
ShapeDrawable(<shape>):通过颜色构造的图形。
LayerDrawable(<layer-list>):表示一种层次化的Drawable集合。
StateListDrawable(<selector>):也是表示Drawable集合,每个Drawable对应着View的一种状态,系统会根据View的状态选择合适的Drawable。
LevelListDrawable(<level-list>):Drawable集合,集合中的每个Drawable都有一个等级(level)概念,根据不同等级切换不同的Drawable。
InsetDrawable(<inset>):将其他Drawable内嵌到自己当中,并可以在四周留出一定间距。
ScaleDrawable(<scale>):根据自己的等级(level)将指定的Drawable缩放到一定比例。
ClipDrawable(<clip>):根据自己的等级(level)将指定的Drawable裁剪成另一个Drawable。

3.自定义Drawable
Drawable使用范围很单一,一个是作为Imageview中的图像来显示。另外一个就是作为View的背景。核心是draw方法。通常没必要自定义Drawable,只在特殊情况下需自定义Drawable。

七、Android动画深入分析
1.View动画
<alpha>、<scale>、<translate>、<rotate>,建议在xml中定义
自定义View动画只需继承Animation这个抽象类,重写它的intialize和applyTransformation方法。
帧动画:通过XML定义一个AnimationDrawable,然后作为View的背景并通过Drawable来播放动画。

2.View动画的特殊使用场景
LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样当它的子元素出场时都具有这种动画效果。
Activity的切换效果用overridePedingTransction这个方法,必须在startActivity或finish之后被调用才生效。

3.属性动画
ValueAnimator、ObjectAnimator、AnimatorSet
TimeInterpolator为时间插值器,根据时间流逝的百分比来计算出当前属性值改变的百分比。
属性动画提供以下监听接口:AnimatorUpdateListener、AnimatorListener
对任意属性做动画须满足:Object必须要提供SetAbc方法,如果动画的时候没有传递初始值,那么还要提供getAbc方法;Object的setAbc对属性abc所做的改变必须能够通过某种方法反映出来。

4.使用动画的注意事项
OOM问题、内存泄露、兼容性问题、View动画的问题、不要使用px、动画元素的交互、硬件加速

八、理解Window和WindowManager
Window是一个抽象类,具体实现是PhoneWindow,可通过WindowManager创建一个Window。WindowManager是外界访问Window的入口,window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。Android中所有视图都是通过Window来呈现的,Window实际是View的直接管理者。
1.window和windowManager
WindowManager常用三个方法:添加View、更新View和删除View。这三个方法定义在ViewManager中,而WindowManager继承它。

2.Window的内部机制
Window是一个抽象的概念,每一个window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,它是以View形式存在。
添加过程:1.检查参数是否合法,如果是子Window那么还需要高速一些布局参数;2.创建ViewRootImpl并将View添加到列表中;3.通过ViewRootImpl来更新界面并完成Window的添加过程
删除过程:和添加过程一样,都是先通过WindowManagerImpl后再进一步通过WindowManagerGlobal来实现的。
更新过程:首先更新View的LayoutParams并替换掉老的LayoutParams,接着再更新ViewRootImpl中的LayoutParams,再重新对View布局和更新Window视图。

3.Window的创建过程
Activity:1.如果没有DecorView那么就创建它;2.将View添加到DecorView的mContentParent中;3.回调Activity的onContentChanged方法通知Activity视图已经发生改变。
Dialog:1.创建Window;2.初始化DecorView并将Dialog的视图添加到DecorView中;3.将DecorView添加到Window中并显示。
Toast:通过Toast中的TN这个类实现,它有两个方法show和hide,运行在Binder线程池中,内部使用了Handler。

九、四大组件的工作过程
1.四大组件的运行状态

2.Activity的工作过程
startActivity --> startActivityForResult --> Instrumentation.execStartActivity --> ActivityManagerNative.getDefault.startActivity --> AMS.startActivity --> ActivityStackSupervisor.startActivityMayWait --> ActivityStack.resumeTopActivitiesLocked --> ActivityStackSupervisor.realStartActivitiesLocked --> ApplicationThread.scheduleLaunchActivity --> handleLaunchActivity --> performLaunchActivity:1.从ActivityClientRecord中获取待启动的Activity的组件信息;2.通过Instrumentation的newActivity方法使用类加载器创建Activity对象;3.通过LoadedApk的makeApplication方法来尝试创建Application对象;4.创建ContextImpl对象并通过Activity的attach方法来完成一些重要数据的初始化;5.调用Activity的onCreate方法。

3.Service的工作过程
启动过程:ContextWrapper.startService --> startServiceCommon --> AMS.startService --> ActivityService.startServiceLocked --> startServiceInnerLocked --> bringUpServiceLocked --> realStartServiceLocked --> ApplicationThread.ScheduleCreateService ---> Service.onCreate
绑定过程:ContextWrapper.bindService --> bindServiceCommon --> AMS.bindService --> ActivityService.bindServiceLocked --> bringUpServiceLocked --> realStartServiceLocked --> ApplicationThread.ScheduleCreateService ---> Service.onCreate --> requestServiceBindLocked --> ApplicationThread.ScheduleBindService --> handleBindService --> publishService --> ServiceDispatcher.InnerConnection --> ServiceDispatcher.connected --> RunConnection --> onServiceconnected

4.BroadcastReceiver的工作过程
注册:静态注册由PMS完成。动态:ContextWrapper.registerReceiver --> registerReceiverInternal --> ReceiverSidpatch.getIntentReceiver
发送接收:ContextWrapper.sendBroadcast --> AMS.broadcastIntent --> broadcastIntentLocked --> BroadcastQueue.Schedule.BroadcastLocked --> processNextBroadcast --> delivertoRegisteredReceiverLocked --> performReceiverLocked --> ApplicationThread.scheduleRegisteredreceiver --> InnerReceiver --> perforReceiver --> App.run

5.ContentProvider的工作过程
query:acquireProvider --> main --> AMS.attachApplication --> attachApplicationLocked --> bindApplication --> handleBindApplication:1.创建ContextImpl和Instrumentation;2.创建Application对象;3.启动当前进程的ContentProvider并调用其onCreate方法;4.调用Application的onCreate方法。

十、Android的消息机制
从开发角度来说,Handler是Android消息机制的上层接口。
Android消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue是消息队列,内部存储了一组消息,以队列的形式对外提供插入和删除的工作。Looper是消息循环,有新消息就会去处理。Looper中的ThreadLocal不是线程,什么用是可以在每个线程中存储数据,可以在不同线程中互不干扰地存储并提供数据。线程是默认没有Looper的,UI线程(ActivityThread)被创建时会初始化Looper,所以主线程默认可使用Handler。

1.Android的消息机制概述
Handler的主要作用是将一个任务切换到指定的线程中去执行。
系统之所以提供Handler,主要原因就是为了解决在子线程中无法访问UI的矛盾。
系统为什么不允许子线程访问UI呢?这是因为Android的UI控件不是线程安全的。
Handler可通过post或send方法发送消息到Looper中处理。send被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新消息时就会处理新消息,最终消息中的Runnable或Handler的handlerMessage方法就会被调用。Looper是运行在创建Handler所在线程中的。

2.Android中的消息机制分析
ThreadLocal在被访问get时,内部会在各自线程中取出一个数组,然后再从数组中根据当前ThreadLocal索引去查的对应的handler
enqueueMessage的主要操作是单链表的插入操作。next是一个无限循环方法,如果MessageQueue中没有消息,next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这个方法并将其从单链表中移除。
Looper会不停地从Messagequeue中查看是否有新消息,如果有新消息就会立即处理,否则就一直阻塞在那里。
Looper.prepare可创建一个Looper,然后通过Looper.loop来开启消息循环。
Handler发送消息是向消息队列中插入一条消息,MessageQueue的next方法将返回这条消息给Looper,Looper得到消息后进行处理,最终消息由Looper交给Handler处理,即Handler的dispatch方法被调用。
Handler处理消息:1.检查Message的callback是否为null,不为null就通过handleCallback来处理消息;2.检查mCallback是否为null,不为null就调用mCallback的handleMessage来处理消息;3.调用Handler的handleMessage方法来处理消息。

3.主线程的消息循环
主线程通过Looper.lop()来开启主线程的消息循环,之后通过ActivityThread.H和消息队列进行交互,UI线程通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成UI线程的请求后回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中执行。

十一、Android的线程和线程池
AsyncTask封装了线程池和Handler,主要是为了在子线程中更新UI,Handler Thread是一种具有消息循环的线程,内部可以使用Handler。IntentService内部用HandlerThread来执行任务。
当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,Android中的线程池来源于Java,主要是通过Executor来派生特定类型的线程池。

1.主线程和子线程

2.Android中的线程形态
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI,但并不适合进行特别耗时的后台任务,对特别耗时的后台任务,建议使用线程池。
工作原理:execute --> executeOnExecutor --> SerialExecutor.THREAD_POOL_EXECUTOR
HandlerThread继承了Thread,它是一种可以使用Handler的Thread实现:在run方法中云视通Looper.prepare()来创建消息队列,并通过Looper.loop来开启消息队列,这样在实际的使用中就允许在handlerThread中创建Handler了。
IntentService适合执行一些发生在后台任务,第一次启动时,onCreate方法会被调用,onCreate会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象。IntentService在onStartCommand中处理每个后台任务的Intent。

3.Android中的线程池
ThreadPoolExecutor是线程池的真正实现,主要参数:corePoolSize、maximnumPoolSize、keepAliveTime、unit、workQueue、threadFactory。
newFixedthreadPool是一种线程数量固定的线程池,当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭了。
newCachedThreadPool是线程数量不定的线程池,只有非核心线程,并且最大线程数为Integer.MAX_VALUE。
newScheduledThread核心线程数是固定的,非核心线程是没有限制的,并且当非核心线程闲置时会被立即回收。
newSingleThread内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。

十二、Bitmap的加载和Cache
1.Bitmap的高效加载
Bitmap在Android中指一张图片,格式可为png、jpg等等。BitmapFactory提供以下方法:decodeFile、decodeResource、decodeStream、decodeByteArray,分别用于从文件系统、资源、输入流、字节数组中加载一个Bitmap对象,其中前两个又间接调用了decodeStream方法。
高效加载的核心思想是采用BitmapFactory.Option来加载所需尺寸的图片。主要用到它的inSampleSize参数,即采样率。

2.Android的缓存策略
LruCache是一个泛型类,内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较高使用的缓存对象,然后再添加新的缓存对象。
DiskLruCache用于实现磁盘缓存,它通过将缓存对象存入文件系统,从而实现缓存的效果。open、Edit、get
列表项优化:不要在getview中执行大学时操作,控制异步任务的执行频率,开启硬件加速。

十三、综合技术
1.使用CrashHandler来获取应用的Crash信息
首先实现一个UncaughExceptionHandler对象,在它的uncaughtException方法中获取异常信息,并将其存储在SD卡中或者上传到服务器供开发人员分析,然后调用thread的setDefaultUncauthExceptionHandle方法将它设置为线程默认的异常处理器。在Application初始化的时候为线程设置CrashHandler。

2.使用multidex来解决方法数越界

3.Android的动态加载技术

4.反编译初步
使用dex2jar和jd-gui反编译,使用apktool进行打包。

十四、JNI和NDK编程
1.JNI的开发流程
在Java中声明native方法;编译Java源文件得到class文件,然后通过java命令导出JNI的头文件;实现JNI方法;编译so库并在Java中调用。

2.NDK的开发流程
下载并配置NDK;创建一个Android项目,并声明所需的native方法;实现Android项目中所声明的native方法,切换到jni目录的父目录,然后通过ndk-build命令编译产生so库。

3.JNI的数据类型和类型签名

4.JNI调用Java方法的流程
先通过类名找到类,然后再根据方法名找到方法的id,最后就可以调用这个方法。如果调用了Java中的非静态方法,那么需要构造出类对象后才能调用它。

十五、Android性能优化
1.性能优化方法
布局优化:<include>标签、<merge>标签、ViewStub
绘制优化:View的onDraw方法,避免执行大师的操作
内存泄露优化:静态变量导致内存泄露;单例导致内存泄露;属性动画导致内存泄露
响应速度优化和ANR日志分析:trace文件
ListView和Bitmap优化
线程优化:采用线程池
建议:避免创建过多的对象;不要过多使用枚举,枚举占用内存比整型大;常量使用static final来修饰;使用一些Android我有的数据结构,比如SparseArray和Pair等,它们都具有更好的性能;适当使用软引用和弱引用;采用内存缓存和磁盘缓存;尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄露。

2.内存泄露分析之MAT工具

3.提高程序的可维护性
命名要规范、代码的排版上需要留出合理的空白来区分不同的代码块,仅为添加注释、恰当使用设计模式。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 141,857评论 18 610
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 104,046评论 12 125
  • 阿漾、小美、阿华、大米、阿洛在同一家公司上班的五个年轻人,因为常常分在一组做事情,彼此之间较为熟悉,相对其他同事,...
    沈还乡阅读 124评论 3 3