[笔记]Android部分技术点目录(1)

目录

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的层级、测量、和绘制

  1. 层级
    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 重绘(只绘制需要重绘的)
  2. 测量
    measure,是final的,调了onMeasure
    onMeasure,可重写,调了setMeasuredDimensions
    setMeasuredDimensions,真正给MeasuredWidth和MeasuredHeight赋值
    因为测到父View时,可能不知道子View的大小,所以父View先测量子View,再测量自己。
    如果第一次测量子View的结果超过父控件,会再测一次,所以可能测量两次。
    树形递归,ViewGroup发起,遍历每一个子控件,通过父控件的MeasureSpec和子控件的LayoutParams来测量。
  3. 摆放和绘制
    MeasureSpec,测量规格,是父控件传递给子控件的,是模式和大小的组合值,32位,前两位代表模式(EXACTLY对子容器指定尺寸、AT_MOST对子容器指定最大尺寸、UPSPECIFIED对子容器未指定尺寸),后30位代表尺寸。
    父控件传来的MeasureSpec和LayoutParams共同决定子控件的大小
    View的绘制是在Framwork层处理的
  4. 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驱动完成跨进程通信

事件分发

  1. dispatchTouchEvent
    传到view就一定会调用,返回是否消费此事件,从上往下dispatchTouchEvent
    Activity的dispatchTouchEvent里,会调用onUserInteraction(),主要用于屏保
    返回true的条件是:listener不为空、且listener处理onTouch事件返回true、且控件的enabled状态
    如果返回true,当前事件交给onTouchEvent,后续事件会继续分发过来;
    如果返回false,当前事件回传给上级onTouchEvent,后续事件会继续分发过来
  2. onInterceptTouchEvent
    是否拦截事件,如果拦截了,就调用onTouchEvent;如果不拦截,就交给child做dispatchTouchEvent,并返回child是否消费事件。
    如果自己onTouchEvent,或者child返回消费,就返回事件已消费
    Activity没有onInterceptTouchEvent()方法
    View没有onInterceptTouchEvent()方法
    如果返回true,当前事件交给onTouchEvent,后续事件不会继续分发过来;
    如果返回false,传递给下一级dispatchTouchEvent,后续事件会继续分发过来
    针对一串事件来说,只有前一个返回true,才会继续收到后一个(如果返回false,后续都会被分给上层的onTouchEvent)
  3. 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进程优先级

进程保活

  1. 原因:业务需要,比如必须动态注册的广播 im通信 push 等
    后台进程用lru 判断回收哪个
    后台进程和空进程在内存充足时也可能回收
  2. 回收策略
    Low memory killer. 用Linux的 out of memory 改的,定期运行,用打分机制选择进程回收,分高者回收
    通过OOM_ODJ判断阀值,阀值是进程优先级
  3. 保活方案
    ·监听系统广播 但受权限和拦截影响
    ·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个方面的功能:

  1. 压缩 去除无用类和字段
  2. 优化 字节码去除无用指令
    优化的步骤中,那些非EntryPoint的类、方法都会被设置为private、static或final,不使用的参数会被移除???
  3. 混淆 字节码改无意义名
  4. 预检测 检查处理后的代码(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的这个权限给关掉了,否则影响用户体验。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容