目录
FragmentPagerAdapter和FragmentStatePagerAdapter
IntentService
HandlerThread
View的层级、测量、和绘制
webView的安全漏洞、耗电问题和内存泄露
Binder机制的内核原理和通信机制
事件分发
进程优先级
进程保活
ProGuard原理和功能
AsyncTask的用法和常见问题
Activity和Fragment的生命周期
BroadCastReceiver的基本原理
配置改变与Activity的生命周期
彻底退出App的设计原理
电视App的非系统App不能通过Service做Toast的问题
FragmentPagerAdapter和FragmentStatePagerAdapter
前者适合少量Fragment,它在destroyItem时,是FragmentTransaction.detach(),detach只会执行onPause,onStop,onDestroyView,不会执行onDestroy和onDetach,也就是仍保留Fragment实例;
后者适合大量Fragment,它在detroyItem时,是FragmentTransaction.remove(),会执行onDetroy和onDetach,不再有Fragment实例。
IntentService
定义:
是一个继承了Service的抽象类,继承它之后,可以处理异步任务,优先级还比Service高
封装了HandlerThread和Handler,有一个工作线程来处理耗时操作
任务完成后,IntentService会自动停止,不需要stopSelf(),相应的也可以启动多次(保持一个实例)
每一个耗时操作会在IntentService的onHandleIntent回调方法中执行,每次只执行一个线程,依次执行(串行,HandlerThread的Looper的MessageQueue)。
实现:
1.构造方法,传一个字符串,作为线程名称
2.onHandleIntent(Intent intent),实现异步任务
源码:
onCreate时,创建一个HandlerThread,创建一个ServiceHandler,让这个ServiceHandler引用HandlerThread的Looper对象,这样就可以处理异步任务。
IntentService启动时会回调onStartCommand方法-->onStart方法-->用myserviceHandler发送一个message--> myserviceHandler接收这个message,在handleMessage时,就会调用onHandleIntent方法了,因为myserviceHandler引用了HandlerThread的Looper对象,所以它实际上就是在HandlerThread这个工作线程里调用了onHandleIntent方法。
handleMessage里,调用完onHandleIntent方法后,会调用stopSelf(msg.arg1),有了msg.arg1参数,stopSelf就会在消息处理完后,停止Service
HandlerThread
就是一个Thread线程,内部有一个Looper,可以在onLooperPrepared中,可以重写一点初始化
Handler其实是跟着Looper走的,引用的Looper在哪个线程,Handler就在哪个线程,Handler通过ThreadLocal引用所在线程的Looper,Looper通过msg.target控制Handler运作的。
View的层级、测量、和绘制
-
层级
Activity
PhoneWindow(Window的唯一实现类)
DectorView(是页面的根,继承自FramLayout(所以是ViewGroup),是PhoneWindow的内部类,PhoneWindow把DectorView创建出来,然后用ActivityThread去启动Activity,实现用WindowManager加载View,最终用一个ViewRootImpl去setView,实现加载)
ViewGroup
View/ViewGroup
Activity通过PhoneWindow的setContentView方法来设置布局
View的绘制其实分为三步:
measure 测量
layout 摆放
draw 重绘(只绘制需要重绘的) -
测量
measure,是final的,调了onMeasure
onMeasure,可重写,调了setMeasuredDimensions
setMeasuredDimensions,真正给MeasuredWidth和MeasuredHeight赋值
因为测到父View时,可能不知道子View的大小,所以父View先测量子View,再测量自己。
如果第一次测量子View的结果超过父控件,会再测一次,所以可能测量两次。
树形递归,ViewGroup发起,遍历每一个子控件,通过父控件的MeasureSpec和子控件的LayoutParams来测量。 -
摆放和绘制
MeasureSpec,测量规格,是父控件传递给子控件的,是模式和大小的组合值,32位,前两位代表模式(EXACTLY对子容器指定尺寸、AT_MOST对子容器指定最大尺寸、UPSPECIFIED对子容器未指定尺寸),后30位代表尺寸。
父控件传来的MeasureSpec和LayoutParams共同决定子控件的大小
View的绘制是在Framwork层处理的 -
draw的有两个容易混淆的方法
invalidate()方法,会draw,只绘制需要重绘的,如果没有大小变化,不会layout(setVisibility/requestFocus/setSelection/setEnabled等方法会触发)
requestLayout()方法,会measure和layout,但是不会draw
Android View的绘制流程
Android View源码解读:浅谈DecorView与ViewRootImpl
webView相关
1.js安全漏洞,老版本的webView.addJavascriptInterface方法被滥用,导致js用发射机制,可以调用任何本地方法。
2.在布局文件中,销毁webView时,要先从父控件中remove掉webView,然后用webView.onDestory
3.jsbridge,js和native桥
4.webviewClient.onPageFinished,判断网页是否被加载完成,但是跳转也会有,推荐用webviewClient.onProgessChanged函数
5.线程耗电,可以放在一个单独的进程里,直接用system.exit()来退出整个进程。
6.硬件加速,拖动更顺滑,但是副作用,加载可能有白块。
7.内存泄露,webView关联Activity,而webView自己有独立的线程,原理等同内部类导致的内存泄露。解决方法一个是独立进程,另一个是动态添加webView+弱引用。
Binder
一、内核知识
1.进程隔离/虚拟地址空间
只可以访问许可的资源。
2.系统调用
从用户空间,访问内核层的程序。
3.binder驱动
运行在内核空间,负责用户进程间的交互
二、Binder通信机制
1.背景
.Linux有很多跨进程通信机制。
.Binder比socket更高效、协议本身更安全(对通信双方做身份校验)。
2.通讯模型
·binder驱动好比电话基站(client查到service后,通过binder驱动实现通信)
·ServiceManager好比通信录(service要注册进来)
3.如何跨进程
·service会把带函数的object注册到sm,client通过binder驱动找到sm中有这个函数
·内核中的binder驱动,给client一个代理对象,代理对象的方法是空的,client调用代理对象的函数,binder驱动再去找service来处理,binder驱动是个中转站
·aidl中的transact()函数,是个Native函数,就是在这里调用了底层的binder驱动完成跨进程通信
事件分发
- dispatchTouchEvent
传到view就一定会调用,返回是否消费此事件,从上往下dispatchTouchEvent
Activity的dispatchTouchEvent里,会调用onUserInteraction(),主要用于屏保
返回true的条件是:listener不为空、且listener处理onTouch事件返回true、且控件的enabled状态
如果返回true,当前事件交给onTouchEvent,后续事件会继续分发过来;
如果返回false,当前事件回传给上级onTouchEvent,后续事件会继续分发过来 - onInterceptTouchEvent
是否拦截事件,如果拦截了,就调用onTouchEvent;如果不拦截,就交给child做dispatchTouchEvent,并返回child是否消费事件。
如果自己onTouchEvent,或者child返回消费,就返回事件已消费
Activity没有onInterceptTouchEvent()方法
View没有onInterceptTouchEvent()方法
如果返回true,当前事件交给onTouchEvent,后续事件不会继续分发过来;
如果返回false,传递给下一级dispatchTouchEvent,后续事件会继续分发过来
针对一串事件来说,只有前一个返回true,才会继续收到后一个(如果返回false,后续都会被分给上层的onTouchEvent) - onTouchEvent
从下往上onTouchEvent
dispatchTouchEvent中的判断分支里,会执行onTouchEvent
如果返回true,当前事件不再向下传递,后续也会继续分发过来,会直接传递到view的onTouchEvent,不再经过
如果返回false,当前事件交给上级onTouchEvent,后续不会继续分发过来
ACTION_DOWN及其后续事件的拦截处理
1.如果最底层ViewC在onTouchEvent中处理了ACTION_DOWN的onTouchEvent,就会持续获取后续的其他事件(如果不消费DOWN,其他事件也不会给它)
2.此时,后续的MOVE如果被上层ViewB截取,系统会做一个CANCEL事件传给ViewC,这个MOVE不会给ViewB
3.再后续的MOVE会给ViewB的onTouchEvent(不会再给ViewB的onInterceptTouchEvent,因为在上一步中已经返回过true,就不会再进来了)
4.如果最开始不消耗ACTION_DOWN事件(onTouchEvent()返回了false),那么同一事件序列中的其他事件都不会再交给它处理,并且事件将重新交由它的父元素去处理,及父元素的onTouchEvent()会被调用。
5.如果先消耗了ACTION_DOWN事件,但是后续不消耗ACTON_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent()并不会被调用,并且当前View可以持续收到后续的事件。最终这些消失的点击事件会传递给Activity处理。
onTouch和onTouchEvent的区别
1.都在dispatchTouchEvent中调用
2.在dispatchTouchEvent源码中,先执行onTouch,如果onTouch返回false,才执行onTouchEvent
3.因为listener执行onTouch前有两个&&的判断,所以要看有listener,且控件是enable的,如果控件不是enable的,就只能通过onTouchEvent来处理了
反向回传
如果最底层view不处理事件,会反向回传,最终返还到activity中处理
Activity dispatchTouchEvent --> Activity onUserInteraction --> Viewgroup onInterceptTouchEvent--> View onTouchEvent --> Activity onTouchEvent(如果没有处理,就返回到Activity处理)
Android事件分发机制“不详解”
Android事件分发机制详解:史上最全面、最易懂
进程优先级
进程优先级和GS相关,优先级越高,oom_adj值越低(系统进程为-16),AMS中的进程优先级分为5等:
前台 > 可见 > 服务 > 后台 > 空
- 前台进程 Activte process oom_adj为0
包括正在runningactivity、与该activity绑定的service、正在onReceive的broadcastreceiver、正在onstart/onstop/ondestroy的service
前台响应用户事件的Activity以及与之绑定的Service
startForeground的Service
正在执行onStart,onCreate,OnDestroy的Service
正在执行onReceive的BroadcastReceiver - 可见进程 Visible Process oom_adj为1
包含onpause状态的activity及其绑定的service
也就是onPause但未onStop的Activity
绑定到可见Activity的Service - 服务进程 Service process
普通Service - 后台/背景进程 Background process oom_adj为2
不可见的activity,为onStop后的activity
也就是onStop的Activity - 空进程 Empty process oom_adj为15
不包含任何组件的进程
空进程没有活动状态,是为了缓存启动数据,方便下次启动(例如,关掉重开后的浏览器网页数据),能提高速度,并提供历史数据
Android平台App进程优先级
进程保活
- 原因:业务需要,比如必须动态注册的广播 im通信 push 等
后台进程用lru 判断回收哪个
后台进程和空进程在内存充足时也可能回收 - 回收策略
Low memory killer. 用Linux的 out of memory 改的,定期运行,用打分机制选择进程回收,分高者回收
通过OOM_ODJ判断阀值,阀值是进程优先级 - 保活方案
·监听系统广播 但受权限和拦截影响
·service启动方式(START_STICKY) 但有两种情况失效:连续三次机会5 10 20秒重试失败;主动关闭时
·Native机制 Native不受AMS管理,自由度很高,在Native代码中监听主进程,用定时轮询或文件锁来监控(5.0以上会强杀Group中的所有进程)
·JobSchedular机制 定时API21以后
需要写一个类继承JobService,在onstart里声明创建JobScheduler对象,并用JobInfo建造者模式设置参数
public class MyJobService extends JobService {
private JobScheduler mJobScheduler;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(startId++,
new ComponentName(getPackageName(), JobHandlerService.class.getName()));
builder.setPeriodic(5000); //每隔5秒运行一次
builder.setRequiresCharging(true);
builder.setPersisted(true); //设置设备重启后,是否重新执行任务
builder.setRequiresDeviceIdle(true);
}
return START_STICKY;
}
...
·账户同步机制 跟随账户同步机制来处理
通过提高进程优先级保活
在遵循系统游戏规则的前提下,要实现进程保活,就要提高进程优先级。
-
oom_adj
进程优先级主要由系统维护的oom_adj的值判断,值越低优先级越高(系统进程为-16、前台进程0),值越高优先级越低,越容易回收(空进程15)。
提升进程优先级,就是压低oom_adj的值。 -
查看
在adb中可以用命令查看进程优先级,具体为:
1.adb shell
2.ps 所有进程,找到你的app进程名(默认包名)
3.ps | grep 进程名
4.cat /proc/pid/oom_adj (其中pid是上述grep得到的进程号) -
方法
有多种方法可以提升进程优先级:
1.为application设置属性:android:persistent=”true”
缺点:仅限部分系统App,而且某些平台可能导致无法访问SD卡
2.在onDestroy中重启
缺点:进程被杀时不会触发onDestroy
3.intent-filter中设置android:priority(误)
错误:很多人说android:priority可以提升进程优先级,这是错误的,怎么可能这么容易,根据官方文档说法,It provides information about how able an activity is to respond to an intent that matches the filter, relative to other activities that could also respond to the intent.这是用来给组件排序的,在隐式调用Activity/Service时,如果有多个满足条件的组件对象,就根据android:priority来判断让哪个组件优先响应;在有序广播中也是类似的机制。
4.setForeground绑定到通知栏
缺点:会显示到通知栏中/在App图标上显示数字,需要通过双服务的写法屏蔽显示(微信的做法),即在Service中写一个静态内部Service(在静态内部类在manifest中用$符注册,如:<service android:name=".GrayService$GrayInnerService"/>),两个服务都调用startForeground,并使用同一个notificationid,然后销毁静态内部类,因为静态内部类被销毁,所以通知栏会消失,这实际上是利用了一个系统bug。
5.维持1像素的透明悬浮窗,保持为前台进程,并在recentTask中不显示该Activity。
6.进程拆分,相互唤醒
根据业务聚合,拆分为多进程,进程直接可以相互唤醒。
另外,拆分进程有利于减低内存销毁(如拆分为低消耗网络推送、后台、中消耗主界面、重消耗webview/gallery等),比较耗内存的进程可以主动回收进程,彻底释放,网络推送的进程可以维持低内存消耗避免oom。
7.进程回收时的数据保护
如果进程最终被回收了,就需要保存回收前的数据,可以在onDestroy和onTrimMemory等生命周期中回收数据。
微信Android客户端后台保活经验分享
Android 进程常驻(4)----native保活5.0以上方案推演过程以及代码详述
KeepProcLive开源项目
5.0/6.0以后获取当前运行的进程
让用户授权USEAGE_STATE,或通过读系统的proc文件找oom评分最低的App。
ProGuard原理和功能
ProGuard是一个开源项目,因为Java是跨平台的解释性语言,编译成的字节码里有很多源码信息,这些无用且危险,proGuard可以处理掉这些信息。
ProGuard使用Entrypoint的概念,EntryPoint是不会被处理的类和方法,ProGuard会从EntryPoint开始(所以android.app.Activity等都被写成keep),递归遍历,看哪些类和类成员被使用了,未被使用的在压缩阶段丢弃。
Progard有4个方面的功能:
- 压缩 去除无用类和字段
- 优化 字节码去除无用指令
优化的步骤中,那些非EntryPoint的类、方法都会被设置为private、static或final,不使用的参数会被移除??? - 混淆 字节码改无意义名
- 预检测 检查处理后的代码(Android不需要预检测???)
Android混淆打包那些事儿
AsyncTask
抽象类,需要自己扩展,本质是一个封装了一个静态线程池和handler的异步框架,只能做短耗时操作。
3个参数
int 传入的参数
int 反馈进度
String 返回结果
5个方法
onPreExecute 初始化 UI线程 比如显示进度条
doInBackground 耗时操作,可以调用publishProgress更新进度, return返回结果
publishProgress更新进度
onProgressUpdate 更新进度
onPostExecute 完成操作
四个问题:
1.内存泄露AsyncTask也是非静态内部类
2.生命周期长过Activity,有后台任务,需要显式调用asyncTask.cancel()
3.结果可能丢失
4.并行和串行,1.6~2.3是并行,之前和之后是串行
Activity和Fragment生命周期
Activity生命周期
running paused stoped destroyed
oncreate onstart onresume onpause onstop ondestroy
onrestart
onstart和onresume的区别是,onstart只可见,不可操纵
Fragment
Fragment的完整生命周期
onAttach onCreate onCreateView onActivityCreated onStart onResume onPause onStop onDestoryView onDestroy onDetach
FragmentPagerAdapter和FragmentStatePagerAdapter的区别,就是前者在destroyItem时只是detach移除而不销毁,后者会remove销毁Fragment,所以后者更省内存,适用于管理大量的Fragment
BroadCastReceiver基本原理
普通的是通过AMS作为通信枢纽,通过Binder跨进程通信机制,向AMS发起广播,由AMS分发广播
LocalBroadCastReceiver则使用主线程的Handler进行通信,所以效率更高,更安全。
配置改变与Activity生命周期
如果设置了强制横屏,Activity会经历多次创建和销毁,要避免的话,需要把这个事件抓到onConfigurationChanged里,自己处理。
首先,App需要权限,android.permission.CHANGE_CONFIGURATION,获取系统设置权限,这是一个需要用户确认才能获取的权限
然后,要配置为拦截事件,android:configChanges="orientation|screenSize",官方文档是明确要求加screenSize的,因为screenSize会要求用户选择不同的画面和图像。
有些写法里有keyboard/keyboardHidden,那其实是键盘弹出,跟横屏没关系;
有些写法里有layoutDirection,那其实是配合语言变化的local事件(有些语言要求从右向左书写);
其他的配置变更还有:
mcc/mnc:SIM卡变化
local:变更语言
fontScale:字体缩放
smallestScreenSize:换了物理屏幕,比如外接显示器
uiMode:UI模式切换,比如夜间模式
keyboard:外接键盘
keyboardHidden:键盘变得可以/不可以访问,比如滑出/滑入键盘
screenLayout:屏幕的布局变了,(懵逼,什么意思)
touchscreen:触摸屏变了(基本不应该发生)
navigation:导航类型变了,如增加了键盘后,可以用键盘控制导航方向(基本不发生)
最后,除非自己想做逻辑处理,否则Activity里可以不重写onConfigurationChanged方法。
Android横竖屏切换小结
彻底退出App的设计原理
以前遇到过一个Activity的onResume异常导致自动重启时反复崩溃的问题:
ActivityA启动ActivityB,ActivityB的onResume做一个崩溃异常,然后在Application崩溃时自动重启,就会发现,重启后虽然应该打开ActivityA,但是App还是会尝试打开ActivityB,结果再次崩溃。
直接原因是App退出时,系统会检查Activity栈是否为空,如果为空,会自动重启,所以如果我们要强退App,需要自己维持一个Activity栈,先把所有的Activity全部退出,然后强退整个进程。
至于重建后,界面直接表示的是栈顶的ActivityB不是MainActivity,是因为Application在重建时,会按照栈列表重建,而ActivityB是在Task栈顶的,这样在Application重建后,ActivityB又会执行resume,导致继续崩溃。
但是这个现象令人迷惑,系统为什么这样设计?
其实,这与Android系统的内存管理和进程管理有关。
从系统功能上,Android系统为了跨App灵活跳转,不鼓励开发者主动退出自己的进程。
这就必然带来进程越开越多,内存不够用的情况。
Android因此建立了进程自动管理,视当前内存情况,自动去回收一些进程,按照进程优先级和进程消耗的内存大小,自动回收部分进程。
但这些进程是自动回收的,并非用户操作,所以Android系统会在内存充裕的情况下,再去尝试重建被回收的进程(使用新进程ID)。
重建进程时,会根据Activity栈,判断要重建哪些Activity。
为了在回收和重建过程中,保存和恢复Activity的数据,提供了onSaveInstanceState和onRestoreInstanceState接口(这就是系统“未经许可”销毁你Activity的实际场景)。
电视App非系统App的Service不能Toast的问题
电视App中,跨App启动Service没有问题,但是如果要在Service中Toast,需要Service所在的App作为SystemApp。
这是因为Andriod系统会把通知作为一种权限进行管理,有的手机也会有不能Toast的问题,需要在设置->安全与隐私->通知中心里,开启权限。
电视App遇到这个问题,可能是电视系统默认把非系统App的这个权限给关掉了,否则影响用户体验。