本文主要记录使用单例模式的几种形式,并分析各自的优缺点。使用单例模式可以避免重复创建对象,以此来节省开销,首先了解一下单例模式的四大原则:
创新互联主要从事成都网站设计、成都网站建设、网页设计、企业做网站、公司建网站等业务。立足成都服务荥阳,10年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18982081108
常用的单例模式有:饿汉模式、懒汉模式、双重锁懒汉模式、静态内部类模式、枚举模式,我们来逐个解释这些模式的区别。
关于 volatile 修饰符,又是一个内容,需要理解:
参考(有例子,比较好理解): ,
静态内部类单例模式的优点:
那么有人会问了,如果有多个线程同时访问 getInstance() 方法,会多次初始化类,然后创建多个对象吗?答案是不会的,这我们需要了解一下类的加载机制:
虚拟机会保证一个类的clinit()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的clinit()方法,其他线程都需要阻塞等待,直到活动线程执行clinit()方法完毕。
所以如果一个类的clinit()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但线程唤醒之后不会再次进入clinit()方法。因为在同一个加载器下,一个类只会初始化一次。)
所以静态内部类单例模式不仅能保证线程的安全性、实例的唯一性、还延迟了单例的实例化。
但是静态内部类单例模式也有一个 缺点 ,就是无法传递参数。因为它是通过静态内部类的形式去创建单例的,所以外部就无法传递参数进去。
枚举单例模式占用的内存是静态变量的两倍,所以一般都不使用enum来实现单例。
单例有饿汉模式、懒汉模式、双重锁懒汉模式、静态内部类模式、枚举模式这几种形式。
饿汉模式在初始化类时就创建了对象,容易造成资源浪费;懒汉模式在多线程环境下有风险;枚举模式占用内存过高。这三种模式都有明显的弊端,所以一般不去采用。
双重锁懒汉模式使用了 volatile 修饰符,在性能上会差一点点;静态内部类模式无法传递参数。但是这两种方式都能保证实例的唯一性,线程的安全性,也不会造成资源的浪费。所以我们在使用单例模式时,可以在这两种方式中酌情选择。
参考文章:
当按下Android设备电源键时究竟发生了什么?
Android的启动过程是怎么样的?
什么是Linux内核?
桌面系统linux内核与Android系统linux内核有什么区别?
什么是引导装载程序?
什么是Zygote?
什么是X86以及ARM linux?
什么是init.rc?
什么是系统服务?
当我们想到Android启动过程时,脑海中总是冒出很多疑问。本文将介绍Android的启动过程,希望能帮助你找到上面这些问题的答案。
Android是一个基于Linux的开源操作系统。x86(x86是一系列的基于intel 8086 CPU的计算机微处理器指令集架构)是linux内核部署最常见的系统。然而,所有的Android设备都是运行在ARM处理器(ARM 源自进阶精简指令集机器,源自ARM架构)上,除了英特尔的Xolo设备()。Xolo来源自凌动1.6GHz x86处理器。Android设备或者嵌入设备或者基于linux的ARM设备的启动过程与桌面版本相比稍微有些差别。这篇文章中,我将解释Android设备的启动过程。深入linux启动过程是一篇讲桌面linux启动过程的好文。
当你按下电源开关后Android设备执行了以下步骤。
此处图片中step2中的一个单词拼写错了,Boot Loaeder应该为Boot Loader(多谢@jameslast 提醒)
第一步:启动电源以及系统启动
当电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM,然后执行。
第二步:引导程序
引导程序是在Android操作系统开始运行前的一个小程序。引导程序是运行的第一个程序,因此它是针对特定的主板与芯片的。设备制造商要么使用很受欢迎的引导程序比如redboot、uboot、qi bootloader或者开发自己的引导程序,它不是Android操作系统的一部分。引导程序是OEM厂商或者运营商加锁和限制的地方。
引导程序分两个阶段执行。第一个阶段,检测外部的RAM以及加载对第二阶段有用的程序;第二阶段,引导程序设置网络、内存等等。这些对于运行内核是必要的,为了达到特殊的目标,引导程序可以根据配置参数或者输入数据设置内核。
Android引导程序可以在bootablebootloaderlegacyusbloader找到。
传统的加载器包含的个文件,需要在这里说明:
init.s初始化堆栈,清零BBS段,调用main.c的_main()函数;
main.c初始化硬件(闹钟、主板、键盘、控制台),创建linux标签。
更多关于Android引导程序的可以在这里了解。
第三步:内核
Android内核与桌面linux内核启动的方式差不多。内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。
第四步:init进程
init是第一个进程,我们可以说它是root进程或者说有进程的父进程。init进程有两个责任,一是挂载目录,比如/sys、/dev、/proc,二是运行init.rc脚本。
init进程可以在/system/core/init找到。
init.rc文件可以在/system/core/rootdir/init.rc找到。
readme.txt可以在/system/core/init/readme.txt找到。
对于init.rc文件,Android中有特定的格式以及规则。在Android中,我们叫做Android初始化语言。
Action(动作):动作是以命令流程命名的,有一个触发器决定动作是否发生。
语法
1
2
3
4
5
; html-script: false ]
on trigger
command
command
command
Service(服务):服务是init进程启动的程序、当服务退出时init进程会视情况重启服务。
语法
1
2
3
4
5
; html-script: false ]
service name pathname [argument]*
option
option
...
Options(选项)
选项是对服务的描述。它们影响init进程如何以及何时启动服务。
咱们来看看默认的init.rc文件。这里我只列出了主要的事件以及服务。
Table
Action/Service
描述
on early-init
设置init进程以及它创建的子进程的优先级,设置init进程的安全环境
on init
设置全局环境,为cpu accounting创建cgroup(资源控制)挂载点
on fs
挂载mtd分区
on post-fs
改变系统目录的访问权限
on post-fs-data
改变/data目录以及它的子目录的访问权限
on boot
基本网络的初始化,内存管理等等
service servicemanager
启动系统管理器管理所有的本地服务,比如位置、音频、Shared preference等等…
service zygote
启动zygote作为应用进程
在这个阶段你可以在设备的屏幕上看到“Android”logo了。
第五步
在Java中,我们知道不同的虚拟机实例会为不同的应用分配不同的内存。假如Android应用应该尽可能快地启动,但如果Android系统为每一个应用启动不同的Dalvik虚拟机实例,就会消耗大量的内存以及时间。因此,为了克服这个问题,Android系统创造了”Zygote”。Zygote让Dalvik虚拟机共享代码、低内存占用以及最小的启动时间成为可能。Zygote是一个虚拟器进程,正如我们在前一个步骤所说的在系统引导的时候启动。Zygote预加载以及初始化核心库类。通常,这些核心类一般是只读的,也是Android SDK或者核心框架的一部分。在Java虚拟机中,每一个实例都有它自己的核心库类文件和堆对象的拷贝。
Zygote加载进程
加载ZygoteInit类,源代码:/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
registerZygoteSocket()为zygote命令连接注册一个服务器套接字。
preloadClassed “preloaded-classes”是一个简单的包含一系列需要预加载类的文本文件,你可以在/frameworks/base找到“preloaded-classes”文件。
preloadResources() preloadResources也意味着本地主题、布局以及android.R文件中包含的所有东西都会用这个方法加载。
在这个阶段,你可以看到启动动画。
第六步:系统服务或服务
完成了上面几步之后,运行环境请求Zygote运行系统服务。系统服务同时使用native以及java编写,系统服务可以认为是一个进程。同一个系统服务在Android SDK可以以System Services形式获得。系统服务包含了所有的System Services。
Zygote创建新的进程去启动系统服务。你可以在ZygoteInit类的”startSystemServer”方法中找到源代码。
核心服务:
启动电源管理器;
创建Activity管理器;
启动电话注册;
启动包管理器;
设置Activity管理服务为系统进程;
启动上下文管理器;
启动系统Context Providers;
启动电池服务;
启动定时管理器;
启动传感服务;
启动窗口管理器;
启动蓝牙服务;
启动挂载服务。
其他服务:
启动状态栏服务;
启动硬件服务;
启动网络状态服务;
启动网络连接服务;
启动通知管理器;
启动设备存储监视服务;
启动定位管理器;
启动搜索服务;
启动剪切板服务;
启动登记服务;
启动壁纸服务;
启动音频服务;
启动耳机监听;
启动AdbSettingsObserver(处理adb命令)。
第七步:引导完成
一旦系统服务在内存中跑起来了,Android就完成了引导过程。在这个时候“ACTION_BOOT_COMPLETED”开机启动广播就会发出去。
title: '深入理解android2-WMS,控件-图床版'
date: 2020-03-08 16:22:42
tags:
typora-root-url: ./深入理解android2-WMS-控件
typora-copy-images-to: ./深入理解android2-WMS-控件
WMS主要负责两个功能, 一是负责窗口的管理,如窗口的增加删除,层级.二是负责全局事件的派发.如触摸点击事件.
先简单介绍几个重要的类
IWindowSession. 进程唯一的.是一个匿名binder.通过他向WMS请求窗口操作
surface 绘画时,canvas会把内容绘制到surface里.surface是有surfaceFlinger提供给客户端的.
WindowManager.LayoutParams 集成自ViewGroup.LayoutParams.用来指明client端的窗口的一些属性.最重要的是type. 根据这属性来对多个窗口进程ZOrder的排序.
windowToken.向WMS添加的窗口令牌.每个窗口都要有一个令牌.
IWindow. 是client提供给WMS的.继承自binder.WMS通过IWindow对象来主动发起client端的事件.
窗口的本周就是进行绘制所使用的surface,客户端向WMS添加窗口的过程,就是WMS为客户端分配surface的过程.
ui框架层就是使用surface上绘制ui元素.及响应输入事件
WMS负责surface的分配.窗口的层级顺序
surfaceFlinger负责将多个Surface混合并输出.
WMS有SystemServer 进程启动.他和AMS其实是运行于一个进程中的.只是分别有各自的线程.
上边传入了两个handler.这里就使用windowManager的handler来创建WMS.也就是在一个handerThread线程中创建
用来管理每个窗口的事件输入.也就是把输入事件转发到正确的窗口
能获取显示系统的同步信号.用来驱动动画的渲染
所有窗口动画的总管,在mChoreographer的驱动下渲染所有动画
只有PhoneWindowManager一个实现.定义了很多窗口相关的策略.是最重要的成员,比如负责窗口的zorder顺序.
zorder就是各个窗口在z轴的值.越大越在屏幕上层.窗口就是根据zorder值一层一层堆在一起.
可以绘制的屏幕列表.默认是只有1个.
管理所以窗口的显示令牌token,每个窗口都要属于一个token.这里的IBinder 是
表示所有Activity的token. AppWindowToken是WindowToken的子类,这个list的顺序和AMS中对mHistory列表中activity的顺序是一样的 .反应了系统中activity的叠加顺序.也就是说.所有窗口都有WindowToken.而Activity对应的窗口则多了AppWindowToken.
每个窗口都对应一个WindowState.存储改窗口的状态信息(这就和AMS中对每个activity抽象成ActivityRecord一样)
这里的iBinder 是IWIndow类.
Session 是WMS提供给客户端来与WMS进行交互的,这是匿名binder.为了减轻WMS的负担.客户端通过IWindowManager.openSession 拿到他的代理.然后通过代理与WMS交互.每个进程唯一.
客户端通过IWindowSession.add 来添加窗口. iWindowSession 是同aidl形成的.最终到了WMS.addWindow.
这里总的来说就是确立了客户窗口的WindowToken.WindowState.和DisplayContent. 并都保存了起来.同时根据layoutparams.type进行了些窗口等级的判断.
WindowToken将同一个应用组件的窗口安排在一起.一个应用组件可以是Activity,InputMethod.
WindowToken使应用组件在变更窗口时必须与自己的WindowToken匹配.
这里主要是为了处理窗口的层级关系而设立的.
只要是一个binder对象.都可以作为token向wms声明.wms会把这个binder对应起一个WindowToken.其实就是把客户端的binder和wms里的一个WindowToken对象进行了绑定.
因为Activity比较复杂,因此WMS为Activity实现了WindowToken的子类 appwindowtoken.同时在AMS启动Activity的ActivityStack.startActivityLocked里声明token.
然后在activityStack.realStartActivityLocked里在发给用户进程,然后用户在通过这个binder和WMS交互时带过来.
activity则在 activityStack 线程的handleResumeActivity 里把Activity 对应的窗口,加入到wMS中
取消token 也是在AMS中 ,也就是说, AMS负责avtivity的token向WMS的添加和删除.
当然.Activity的 r.appToken 是 IApplicationToken.Stub ,他里边有一系列的窗口相关的通知回调.
这里总结下. AMS在创建Activity的ActivityRecord时,创建了他的appToken,有把appToken传送给WMS.WMS对应匹配为APPWindowToken,最后还把这个appToken发送给activity.因此AMS就通过ActivityRecord就可有直接操作WMS对该窗口的绘制.如图.
每个window在WMS里都抽象成了WindowState.他包含一个窗口的所有属性.WindowState在客户端对应的则是iWidow.stub类.iWidow.stub有很多窗口通知的回调.
WindowState被保存在mWindowMap里.这是整个系统所有窗口的一个全集.
HashMapIBinder, WindowToken mTokenMap .这里是 IApplicationToken(客户端)和WindowToken的映射
HashMapIBinder, WindowState mWindowMap 这里是IWidow(客户端)和WindowState的映射,并且WMS通过这个IWindow 来回调客户端的方法.
上图可以看出.每个activity 只有一个ActivityRecord.也只有一个AppToken,也就只有一个WindowToken.而一个acitvity可能有多个窗口.每个窗口对应一个WindowState.
WindowToken用来和AMS交换. 而WindowState对应的iWindow则是WMS来与客户端交互的.
窗口显示次序就是窗口在Z轴的排了.因为窗口是叠加在一起的.因此就需要知道哪些显示在上边,哪些在下边.这个由WindowState构造时确定
可见.分配规则是由WindowManagerPolicy mPolicy来决定的.产生 mBaseLayer和mSubLayer. mBaseLayer决定该窗口和他的子窗口在所有窗口的显示位置. mSubLayer决定子窗口在同级的兄弟窗口的显示位置.值越高.显示约靠上.
WindowState 产生了他自己这个窗口的layer值后.在添加窗口的时候就会把所有窗口按layer排序插入mWindows列表中,在通过 adjustWallpaperWindowsLocked();进行层级调整.
当客户端通过IWindowsession.add后,客户端还没有获得Surface.只有在执行IWindowsession.relayout后.客户端才获得了一块Surface. IWindowsession.relayout根据客户端提供的参数,为客户端提供surface.具体实现是WMS.relayoutWindow
总的来说就是根据用户传入的参数,更新WindowState.然后遍历所有窗口布局.在设置合适的Surface尺寸,在返回给用户端
performLayoutAndPlaceSurfacesLocked 会循环调用6次.里边的逻辑大概如下
这里主要下,因为之前加了锁.requestTraversalLocked他又会重复执行performLayoutAndPlaceSurfacesLocked();因此会重复循环执行布局.
布局这部分就记个原理吧
布局完成后.客户端的尺寸和surface都得到了.就可以绘制 了.WMS会通知客户端布局发送变化
总结,WMS 负责管理所有的窗口.包括系统窗口和APP窗口,而窗口必须有一个WindowToken所为标识符.同时WMS为每个窗口创建一个WindowState类,这是窗口在服务端的抽象.WindowState则绑定了一个客户端的IWindow类,WMS通过这个IWindow 向APP发送消息.
AMS在启动Activity的时候.把ActivityRecord.token 通过wms.addtoken 注册到WMS.又把这个token发送到APP端.因此三方可以通过这个token正确找到对应的数据.
WMS负责给所以窗口按ZOrder排序,确定窗口的尺寸,提供绘画用的surface.
Activity的窗口是先wms.addtoken 建立windowToken关系 . wms.addWindow. 添加串口, WMS.relayout获取surface. 完成 .
一个windowToken对应一个Activity. 但是可能对应多个windowSatate.也就是对应多个窗口.
是view树的根实现类是viewRootImpl.但是他不是view.他是用来和WMS进行交流的管理者.viewRootImpl内部有个IWindowSession,是WMS提供的匿名binder,同时还有个iWindow子类,用来让WMS给viewr发消息. view通过ViewRoot向WMS发消息.WMS在通过IWIndow 向APP发消息. 每个View树只有一个ViewRoot,每个Activity也只有一个ViewRoot. UI绘制,事件传递.都是通过ViewRoot.
.实现类是PhoneWindow . Activity和View的沟通就是通过Window.Activity实现window的各种回调.一个Activity也对应一个PhoneWindow.也对应一个View树.
Docerview 就是View树的根.这是一个View. 他由PhoneWindow管理. 下文的WindowManager也由phoneWindow管理.
他还管理window的属性 WindowManager.layoutparams.
他是一个代理类.他集成自ViewManager.他的实现是WindowManagerImpl.这是每个Activity都有一个.但是他只是把工作委托给了 WindowManagerGlobal来实现. 他负责添加删除窗口,更新窗口.并控制窗口的补件属性WindowManager.Layoutparams.
是进程唯一的.负责这个进程的窗口管理.他里边有三个集合.保存这个进程所有窗口的数据.这里的每个数据根据index得到的是同一个Activity属性.所有的WindowManager的操作都转到他这里来.
private final ArrayListView mViews 每个view是个跟节点
private final ArrayListViewRootImpl mRoots view对应的viewRoot
private final ArrayListWindowManager.LayoutParams mParams 窗口的layoutparams属性.每个窗口一个
对于一个acitivity对象永远对应一个PhoneWindow,一个WindowManagerImpl,一个WMS端的APPWindowToken,一个AMS里的ActivityRecord(但是如果一个activity在栈里有多个对象,就有多个ActivityRecord和AppWindowToken),acitvity 的默认窗口的view树是DocerView.
一个窗口 对应一个ViewRoot,一个View树.一个WindowManager.LayoutParams,一IWindow(WMS回调app).一个WSM端的WindowSatate.
但是一个Activity可以有多个窗口,因此对应WMS里可能有多个WindowSatate.这些WindowState都对应一个AppWindowToken.
一个Activity可能被加载多次.因此在AMS中可能有多个ActivityRecord对应这个activit的多个对象.
但是一个进程则对应一个WindowManagerGlobal.一个ActivityThread(主线程).一个ApplicationThread(AMS调用app).一个iWindowSession(viewroot向WMS发消息)
这里的区别就是 app与AMS 的交互是以进程之间进行通信.而App与WMS的交互.则是以窗口作为通信基础.
当Activity由AMS启动时.ActivityThread 通过handleResumeActivity执行resume相关的操作.这个函数首先是执行activity.resume, 此时activity 对应的view树已经建立完成(oncreate中建立,PhoneWindow也创建了).需要把activity的窗口添加到WMS中去管理.
这里的wm是WindowManager.是每个activity一个.他内部会调用WindowManagerGlobal.addView
WindowManagerGlobal.addView
这里会为窗口创建ViewRootImpl. 并把view.ViewRootImpl.WindowMa.LayoutParams都保存在WindowManagerGlobal中, 并通过ViewRootImpl向WMS添加窗口
如果这个窗口是子窗口(wparams.type = WindowManager.LayoutParams.FIRST_SUB_WINDOW
wparams.type = WindowManager.LayoutParams.LAST_SUB_WINDOW)
就把子窗口的token设为父窗口的token否则就是所属activity的token.
在来个图
在这里我们看到.我们通过mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 拿到的并不是远程的WMS.而是本地的WindowManagerImpl. 他又把请求转发给WindowManagerGlobal ,而WindowManagerGlobal作为进程单实例.又是吧请求转给对应窗口的ViewRootImpl.ViewRootImpl通过WMS的IWindowSession 把数据发给WMS.
ViewRootImpl用来沟通View和WMS.并接受WMS的消息.这是双向的binder通信.作为整个空间树的根部,控件的测量,布局,绘制,输入时间的派发都由ViewRootImpl来触发.
ViewRootImpl由WindowManagerGlobal创建,是在activityThread.handleResumeActivity时,先执行activity.resume.在调用wm.addView. 就会执行WindowManagerGlobal.addView里.创建ViewRootImpl,此时是在ui线程中.
ViewRootImpl里的mView属性.host属性,就是view树
添加窗口时通过requestLayout();向ui线程发送消息.最后回调到ViewRootImpl.performTraversals.他是整个ui控件树,measure.layout.draw的集合.
这里分为五个阶段.
预测量阶段.进行第一次测量,获得view.getMeasuredWitdh/Height,此时是控件树期望的尺寸.会执行View的onMeasure
布局阶段,根据预测量的结果,通过IWindowSession.relayout向WMS请求调整窗口的尺寸这会使WMS对窗口重新布局,并把结果返回给ViewRootImpl.
最终测量阶段, 预测量的结果是view树期望的结果.WMS可能会进行调整,在这里WMS已经把结果通知了ViewRootImpl.因此这里会窗口实际尺寸performTraversals进行布局.view及子类的onMeasure会被回调
布局阶段. 测量完成后获得空间的尺寸,布局要确定控件的位置,View及子类的onLayout会被回调.
绘制阶段,使用WMS提供的surface.进行绘制,View及子类的onDraw会被回调.
通常我们看到的都是 先measure,在layout在draw. 这里看到.其实measure先得到期望值,在和WMS沟通.WMS在调整后,返回确定值,在根据确定值进行mesure.
measureHierarchy里会通过三次协商.执行performMeasure 来确认合适的尺寸.
performMeasure 会调用view 的measure 优会调用onMeasure. 我们可重写onMeasure来实现测量.而measure 方法是final的.onMeasure 的结果通过setMeasuredDimension方法保存.
对于view. onMeasure.比较容易. 对于ViewGroup.则还要遍历调用他所以子view的measure. 并且需要考虑padding和子view 的margin. padding是控件外内边距. margin 是控件外边距.
ViewGroup需要先测量完子view.在根据子view的测量值得到自己的宽高.举例,如果只有一个子view.那么ViewGroup的宽= 子view的宽+子view的margin+viewg的padding. 至少是这个值.
继续回到performTraversals
这里就是提前测量了一下.得到控件树希望的尺寸大小,
通过relayoutWindow来布局窗口. ViewRootImpl 通过IWindowSession 来通知WMS进行窗口布局.
这里主要下. 调用WMS后.WMS会调整窗口的尺寸. 同时会生成surface返回给ViewRootImpl. 因此后续的绘画就有了画布了.可以看到最后的参数是mSurface.这是本地的surface. 这里会和wms的进行绑定.
接下来继续performTraversals,绑定WMS返回的surface.然后更新尺寸.
最后进行最终测量. 上边过程太乱了. 了解下就行.还是看常见的控件绘制流程.
绘制由viewRootImpl.performTraversals触发, 抽取出来后,就是这样
就是直接调用view树的根的measure方法. 传入到View
该方法是final .意味着无法重写.这里又会调用onMeasure.
因此.对于view.在onMeasure中调整好高度,通过setMeasuredDimension设置好自己的测量宽高就可以了.
对应ViewGroup.则在onMeasure中,先要遍历子view.调用他们的measure(注意一定是调用子类的measure,measure又会调用onMeasure), 子view宽高都知道后,在根据子view的宽高来设置自己.也就是ViewGroup的宽高受子view影响.
可以看到view的measure又调用了onMeasure, 如果是view 则可以直接重新onMeasure来设定大小.而对于ViewGroup, 则需要重写onMeasure来先遍历子view.设定大小.然后再设定viewGroup的大小. ViewGroup并没有重写onMeasure.因为每个ViewGroup要实现的效果不同,需要自己完成.但ViewGroup提供了几个方法供ViewGroup的继承类来遍历子view.
view的宽高由自己的layoutParams和父view提供的 widthMeasureSpec|heightMeasureSpec共同决定.
View 自己的宽高,是保存在LayoutParams中对,以宽举例 LayoutParams.width 有三种情况,精确值(就是指定大小),MATCH_PARENT. WRAP_CONTENT,模式则有fuview提供.有 unspecified,exactly,at_most三种.
匹配如下.
其实这个很好理解. 如果子view自己指定了宽高.就用他的值就可以.如果子view是match_parent.那就使用父view提供的宽高. 如果子view是wrap_content,那就不能超过父view的值.
看下ViewGroup为子view绘制而提供的方法,可以看到.ViewGroup会减去padding和margin,来提供子view的宽高.
上步measure过程未完成后,整个view书的 测量宽高都得到了.也就是view.getMeasuredWidth()和getMeasuredHeight()
performLayout中会调用mView.layout. 这样就把事件从ViewRootImpl传递到了view.而layout中又会调用onLayout.ViewGroup需要重写onLayout为子view进行布局,遍历调用子view的layout.因此就完成整个view树的laylut过程.
竖向的实现, 竖向的就行把view从上到下一次排开
这里注意区分.measure过程是先得到子view的测量值,在设定父ViewGroup的值.而layout过程则是先传入父view的左上右下值,来计算子view的左上右下的位置值.这里应该具有普遍性.但不知道是否绝对.
performDraw 中的调用draw.又调用mView.draw.然后就进入view树的绘制了.
view的draw 又会调用onDraw ,viewGroup又调用dispatchDraw()把draw分发到子view里 绘制的画布就是canvas. 这是从surface.lockCanvas中获得的一个区域.
而在ViewGroup.dispatchDraw中.重要的一点是getChildDrawingOrder 表示子view的绘制顺序.默认是与ziview的添加顺序一样.我们也可以改变他.最后绘制的会显示在最上边,而这也影响view的事件传递顺序.
view.draw. 就是一层一层的画内容.先画北京,在onDraw.在画装饰什么的.
canvas.translate(100,300)通过平移坐标系.使之后的内容可以直接在新坐标系中绘制.
这就是ViewGroup在向子view传递canvas的时候.方便多了. 会之前先对其ziview的左上角.那么子view就可以直接从自己坐标轴的(0,0)开始绘制, 绘制完成后ViewGroup在还原原有坐标系.
canvas.save. canvas.restore 用来保存还原坐标系.
view.invalidate.
当某个view发送变化需要重绘时,通过view.invalidate向上通知到ViewRootImpl.从这个view到ViewRootImpl的节点都标记为藏区域.dirty area. ViewRootimpl再次从上到下重绘时,只绘制这些脏区域.效率高.
本来安卓兼容使用键盘,也支持,触摸.二者的输入事件派发不一样.使用键盘时会有个控件处于获得焦点状态.处于触摸模式则由用户决定. 因此控件分为两类.任何情况下都能获得焦点.如输入文本框.只有在键盘操作时才能获得焦点.如菜单,按钮.
安卓里有触摸模式.当发送任意触摸时进入触摸模式.当发送方向键和键盘或者执行View.requestRocusFromTouch时,退出触摸模式.
获取焦点. view.request.
先检查是否能获取焦点,
然后设置获取简单的标记,
向上传递到ViewRootimpl.保证只能有一个控件获取焦点.
通知焦点变化的监听者.
更新view的drawable状态,
requestChildFocus会把焦点事件层层上报取消原来有焦点的控件.最后的效果就是从viewrootimpl中.到最终有焦点的view.构成一条 mFoucued 标识的链条.来个图就明白了.每个view的mFocused总是指向他的直接下级.
获取focus的传递是从底层view到顶层的ViewRootImpl.而取消focus测试从顶层的ViewRootimpl到底层原来那个获得焦点的view.
而如果是ViewGroup请求获取焦点,会根据FLAG_MASK_FOCUSABILITY特性来做不同方式,分别有先让自己获取焦点,或者安卓view的索引递增或者递减来匹配view.
ViewRootImpl 中的.WindowInputEventReceiver接受输入事件.他会把事件包装成一个QueuedInputEvent.然后追加到一个单链表的末尾.接着重头到尾的处理输入事件,并通过deliverInputEvent完成分发.这里会把单链表所有事件都处理完.
deliverInput中又会把触摸事件执行到通过 ViewPreImeInputStage.processKeyEvent. 转入mView.dispatchPointerEvent(event).这里又进入 dispatchTouchEvent
MotionEvent是触摸事件的封装.getAction可以拿到动作的类型和触控点索引号.
getX(),getY().拿到动作的位置信息.通过getPointID拿到触控点的id. 动作以down 开头.跟多个move.最后是up.
,当事件返回true.表示事件被消费掉了.
Android开发,需要掌握以下知识:
android以java为基础的,所以前提要学好Java基础知识,比如基本类型、集合等。
android api,学习基本的Activity、service、intent等基本的知识,可以开发一些界面。
计算机网络基本知识。
Linux命令、C编程基础、Android Java编程、Google Android Linux操作系统具体操作等
安卓系统开发的方法,简单来说分成四层:
第一层,以Inventor为代表的绘图工具,是Google推出的简单开发工具,主要是针对初级玩家的玩意儿,操作起来确实容易,一个不懂程序开发的用户就可以通过拖拽搞出一个能在安卓平台上跑的应用来,有点像做PPT,但任何事情都有两面性,这种容易上手的绘图工具,无法实现业务逻辑,运行效率也比较低。
第二层,以Rexsee为代表的无线中间件,这种方法就不是玩家用的了,必须是工程师来用,但对技术门槛的要求很低,会用HTML和JS的技术员就可以方便的使用,在技术要求大幅度降低的同时,基础功能的封装也是一大亮点,这些中间件已经把所有应用需要的基础功能封装好,程序员直接使用JS去调用就可以了,不再需要吭哧吭哧从零开始写代码,比如你想调用个GPS,本来要编几千行的代码,用中间件只需一行JS代码即可搞定,难怪说做中间件的厂商都说:“用了我的东西,你的程序已经做了一大半啦!”此言不虚。
第三层,基于JAVA的JDK JDK(Java Development Kit),目前绝大部分应用都是用这种方式来开发,对程序员的要求比较高,首先要有比较好的JAVA底子,然后要对Android平台本身有很深的研究,门槛不算低。
第四层,基于C++的NDK( Native Development Kit),很多大型游戏是用这种方法开发的,相对于JDK,这种方法的门槛就更高了,目前使用的比较少,毕竟现在是智能手机的时代,硬件和网速都大幅提升,没必要动不动就Touch底层。
看了一段时间关于SystemServer进程的博客,有点小理解,写一篇关于SystemServer的小笔记,然后走一遍过程。
ZygoteInit通过startSystemServer方法fork了一个SS进程。这个进程有啥作用呢。
handlerSystemServerProcess()方法只要是以下三个方法:
其中 applicationInit() 很有意思很重要。该方法中有一个,invokeStaticMain方法通过反射调用main方法:
run方法最终通过反射调用SystemServer的main方法,作用是:
通过以上分析其实main方法的主要作用是:
1、调整系统时间
2、设置属性persist.sys.dalvik.vm.lib.2的值为当前虚拟机的运行库路径
3、装载libandroid_servers.so库,初始化native层service
4、初始化系统Context
5、创建SystemServiceManager对象
6、调用startBootstrapServices(),startCoreServices(),startOtherServices()启动所有的Java服务
另外也可以看到为什么说handler默认是主线程,以及android 应用本身就是基于handler/Looper/Message的
startBootstrapServices():启动java层的各种服务。framwork层的服务。例如AMS
startCoreServices:启动核心服务:
startOtherServices也与上面一样启动各种服务。
总结下:SystemServer进程最终会执行到SystemServer类中的main方法中,初始化各种服务器,其中第一个初始化的就是ActivityManagerService。当我们点击启动app的时候。Zygote会对这个消息进行处理,最终执行到applicationInit。那么是在哪里调用方法启动应用的呢?
Handler是Android消息机制的上层接口。通过它可以轻松地将一个任务切换到Handler所在的线程中去执行。通常情况下,Handler的使用场景就是 更新UI 。
在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新UI。
Handler消息机制主要包括: MessageQueue 、 Handler 、 Looper 这三大部分,以及 Message 。
从上面的类图可以看出:
MessageQueue、Handler和Looper三者之间的关系: 每个线程中只能存在一个Looper,Looper是保存在ThreadLocal中的。 主线程(UI线程)已经创建了一个Looper,所以在主线程中不需要再创建Looper,但是在其他线程中需要创建Looper。 每个线程中可以有多个Handler,即一个Looper可以处理来自多个Handler的消息。 Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。
在子线程执行完耗时操作,当Handler发送消息时,将会调用 MessageQueue.enqueueMessage ,向消息队列中添加消息。 当通过 Looper.loop 开启循环后,会不断地从消息池中读取消息,即调用 MessageQueue.next , 然后调用目标Handler(即发送该消息的Handler)的 dispatchMessage 方法传递消息, 然后返回到Handler所在线程,目标Handler收到消息,调用 handleMessage 方法,接收消息,处理消息。
从上面可以看出,在子线程中创建Handler之前,要调用 Looper.prepare() 方法,Handler创建后,还要调用 Looper.loop() 方法。而前面我们在主线程创建Handler却不要这两个步骤,因为系统帮我们做了。
初始化Looper :
从上可以看出,不能重复创建Looper,每个线程只能创建一个。创建Looper,并保存在 ThreadLocal 。其中ThreadLocal是线程本地存储区(Thread Local Storage,简称TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。
开启Looper
创建Handler :
发送消息 :
post方法:
send方法: