前言
信任做运用层事务开发的同学,都跟我相同,对Framework”深恶痛绝“。确实如此,假如素日里都在做运用层的开发,那么基本上咱们很少会去碰Framework的常识。但生活所迫,面试总是逃不过这一关的,所以作为一名合格的打工人,咱们仍是必须得具备一些Framework的基本常识。
网上有许多文章或浅或深地讲了Framework,有许多源码我们必定也都看过不止一两次,但过一段时刻就忘记了。我这篇文章将会从运用层开发的视角,罗列下我以为需求掌握的Framework基本常识。为了更便利我们去回想与巩固,我更多的是从流程上进行解说,而不会对源码进行十分深入的解读,文章会比较长,我们能够作为一个常识小册,依据目录针对性地进行浏览(基于Android 8.0/9.0)。
那话不多说,咱们冲!
一、体系发动流程
Zygote进程
Zygote进程是十分重要的一个进程,它担任Android体系中虚拟机(DVM或许ART)的创立、运用进程的创立以及SystemServer进程的创立。
体系在开机后会发动init进程,init进程进而会fork出Zygote进程。Zygote进程在发动时,首要做了这么几件作业:
- 创立虚拟机。当Zygote以fork本身的办法去创立运用进程和SystemServer进程时,它们就能拿到虚拟机的一个副本。
- 作为Socket服务端监听AMS恳求创立进程
- 发动SystemServer进程
SystemServer进程
SystemServer进程是咱们学习Framework中最重要的一个体系级进程,咱们熟知的许多服务(AMS、WMS、PMS等)都归于它供给的一个服务,是与咱们APP进程通讯最频繁的进程。
SystemServer进程在发动时,首要做了这么几件作业:
- 发动Binder线程池,为进程间通讯做准备
- 发动各种体系服务,比方AMS、WMS、PMS
Launcher进程
Launcher进程实践上便是咱们的桌面进程,熟悉它的同学都知道一页桌面本质上便是一个Grid类型的RecylerView,那它是怎样创立的呢?
这便是体系发动的终究一步,当SystemServer进程发动了AMS进程后,AMS进程会发动Launcher进程。Launcher进程经过与PMS进程通讯,获取到机器上一切的安装包信息后,将数据(APP图标、APP名称等)烘托到桌面上。
小结:
体系发动流程能够用下图归纳:
二、运用进程发动流程
前面咱们讲了SystemServer进程与桌面进程是如何发动的,等它们发动好了之后,就能够从桌面发动咱们的运用进程了。
在桌面点击APP图标后,Launcher进程将会向AMS恳求创立APP的发动页面。AMS识别到APP进程不存在之后,就会用Socket的办法向Zygote进程恳求创立APP进程。Zygote经过fork的办法创立APP进程之后,会反射调用android.app.ActivityThread
的main()
办法,初始化ActivityThread。
ActivityThread,能够了解为办理APP主线程任务的一个类。在main()
办法中初始化时,咱们会初始化主线程的音讯循环,然后接纳主线程需求处理的一切音讯,确保UI在主线程的烘托(音讯机制后文会详细介绍)。
public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);//注释1
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler(); //注释2
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
}
private void attach(boolean system, long startSeq) {
...
if (!system) {
...
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq); // 注释3
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
} else {
...
try {
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
...
}
能够看到注释2处会创立主线程Handler,这儿的Handler类名为H
,归于ActivityThread的内部类。后边会多次说到它。
注释1处调用attach
办法时,传入的system参数
为false,所以是会到注释3处的代码的,这儿又回到了AMS,调用AMS的attachApplication
办法。attachApplication
办法又会一向调用到ApplicationThread
的bindApplication
办法,终究一向到ActivityThread.handleBindApplication
办法:
private void handleBindApplication(AppBindData data) {
...
try {
final ClassLoader cl = instrContext.getClassLoader();
// 注释1
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
final ComponentName component = new ComponentName(ii.packageName, ii.name);
mInstrumentation.init(this, instrContext, appContext, component,
data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
...
try {
...
try {
// 注释2
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
// 注释3
installContentProviders(app, data.providers);
...
}
}
// 注释4
mInstrumentation.onCreate(data.instrumentationArgs);
} catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
// 注释5
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
...
}
注释1处会初始化Instrumentation
,Instrumentation类十分重要,后文会介绍到。这儿首要是用它在注释2、注释4、注释5处创立Application并调用Application的onCreate生命周期。创立Application时会先调用Application的attachBaseContext
办法。别的,注释3处咱们能够看到,随着运用进程的发动,ContentProviders也会被发动。
小结:
运用进程发动流程能够用下图归纳:
三、Activity发动进程
上一节咱们现已将APP进程成功发动,但咱们的页面还没有起来,这一节咱们就讲一下Activity是如何发动的。
让咱们回想一下,上一节最开端是Launcher进程恳求AMS创立APP的发动页面,那么Launcher进程的桌面实践上也是一个Activtiy,它发动咱们的发动页面,也是调用的Activity#startActivity()办法。一层层进去后,咱们能够发现,实践上是调用的Instrumentation
的execStartActivity()
办法:
#Activity
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) { //注释1
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options); //注释2
……
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
注释1处能够看到,mParent代表着上一个页面,翻开发动页面时mParent为null,调用注释2处Instrumentation的execStartActivity()办法。 Instrumentation不止履行startActivity,它还担任了一切Activity生命周期的调用:
但它也不是真正的履行者,它只是包装了一下,为什么要这样包装呢?个人了解是由于Instrumentation需求对这些行为加一下监控,它的成员变量mActivityMonitors
便是这个效果。
咱们再进去看Instrumentation的execStartActivity()
办法:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
……
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options); //注释1
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
注释1处实践上是获取了ActivityManager.getService()
去调用的startActivity。那这个ActivityManager.getService()
又是何方神圣呢?再往下走
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);//注释1
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
注释1处咱们能够看到ActivityManager.getService()
终究拿到的便是IActivityManager
。这段代码选用的是AIDL(不了解的同学能够自行学习一下),拿到AMS在APP进程的署理目标IActivityManager
。那么终究调用的startActivity也便是AMS的startActivity了。
AMS在startActivity的进程中也会进行一个比较长的链路,首要是校验权限、处理ActivityRecord、Activity任务栈等,这儿不细说,终究会调用到app.thread.scheduleLaunchActivity
办法。app.thread
实践上是IApplicationThread
,跟之前的IActivityManager相同,它也是一个署理目标,署理的是app进程的ApplicationThread
。ApplicationThread
归于ActivityThread的内部类,咱们能够看它的scheduleLaunchActivity
办法:
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord(); // 注释1
r.token = token;
r.ident = ident;
...
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r); // 注释2
}
注释1处会将发动Activity的参数封装成ActivityClientRecord。注释2处发送音讯给H
。之所以发给H
是由于ApplicationThread本身是一个Binder目标,它履行scheduleLaunchActivity
时处于Binder线程中,所以咱们需求经过H
转换到主线程中来。
H
接纳到LAUNCH_ACTIVITY
音讯后,会调用handleLaunchActivity
办法:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
Activity a = performLaunchActivity(r, customIntent);//注释1
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,//注释2
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
...
performPauseActivityIfNeeded(r, reason);//注释3
} else {
...
}
}
看注释2与注释3处的代码,能够联想到它们必然与Activity生命周期有关,而且这儿也解说了为什么上一个页面的onPause生命周期是在下一个页面的onResume之后调用的。注释1处performLaunchActivity
办法很重要,咱们来看一下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
try {
// 注释1
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation); // 注释2
...
// 注释3
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
...
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme); // 注释4
}
activity.mCalled = false;
// 注释5
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
// 注释6
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
...
// 注释7
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
...
}
注释1处经过反射创立了Activity实例。注释2处调用r.packageInfo.makeApplication
创立Application,假如这儿Application现已在ActivityThread#handleBindApplication()
阶段创立了,就会直接回来。
注释3处调用了Activity的attach
办法,内部会创立PhoneWindow
绑定Activity。注释4设置Activity的主题,注释5调用Activity的onCreate()生命周期,注释6处调用onStart()生命周期,注释7当Activity恢复时调用OnRestoreInstanceState()生命周期。再结合上面有说到的onResume()生命周期,咱们Activity的发动进程就现已讲完了。当然,这儿走的是发动Activity的链路,非发动Activity的逻辑其实大差不差,我们有爱好能够自行看一下源码。
小结
发动页面发动进程中各进程的交互联系:
发动页面发动进程可用下图归纳:
四、Context
APP中究竟有多少个Context呢?答案是Activity的数量+Service的数量+1,1是指Application。
如上图所示,ContextImpl
与ContextWrapper
都承继于Context,而且Context详细的完成类是ContextImpl
,作为ContextWrapper
的成员变量mBase
存在。Activity、Service、Application都承继于ContextWrapper
,都归于Context,但它们都是靠ContextImpl
去完成Context相关的功用的。
ContextImpl详细的创立机遇是在Activity、Service、Application创立的时分,调用attachBaseContext()
办法,详细代码这边就不贴了,感爱好的同学能够自行查阅一下。
Application#getApplicationContext()
办法获取到的是什么呢?
# ContextImpl
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
终究调用到ContextImpl#getApplicationContext()
办法,能够看到终究回来Context的是Application
目标。而fragment#getContext()
回来的Context是Activity
目标。
五、View的作业原理
View与Window是相辅相成的两个存在,本节先介绍View,涉及到Window的常识点能够先不管,下一节会详细介绍。能够先把Window了解成画布,View了解成画,画总是要在画布上才干烘托的,也便是说View必须要借助于Window才干展现出来。
View的制作流程
每一个View都会有一个ViewRootImpl
目标,View制作的起点便是ViewRootImpl的perfromTraversals
办法,在perfromTraversals
内部会调用measure、layout、draw
这三大流程。
measure
是为了丈量出View的尺度巨细,measure又会调用onMeasure
,在onMeasure中又会先对子View进行丈量,终究得到整个View的巨细。measure进程之后,咱们就现已能够调用View#getMeasuredWidth()
与View#getMeasuredHeight()
获取到View的丈量宽高了。
layout
进程决定了View在父容器中的方位与实践宽高,也即是View的四个极点坐标的方位。layout与measure相反,layout是先得到父View的方位,再onLayout
递归下去得到子View的方位的。layout进程后,View#getWidth()
与View#getHeight()
才有值,是View的实践宽高。
draw
是制作的终究一步,调用onDraw
办法将View制作在屏幕上。当然,子View也是会一层一层递归(经过dispatchDraw
办法)调用onDraw
办法往下走的。
Measure
制作流程中比较重要的个人感觉是measure
办法,所以单拿出来说一说。咱们在看measure相关办法的时分一定会看到MeasureSpec
这个参数,它是什么东西呢?
MeasureSpec
是一个32位的int值,高2位代表SpecMode
,低30位代表SpecSize
。前者表明模式,后者表明巨细,用1个值包含了两种意义。咱们能够经过MeasureSpec#makeMeasureSpec(size, mode)
办法组成MeasureSpec
,也能够经过MeasureSpec#getMode(spec)
与MeasureSpec#getSize(spec)
获取到MeasureSpec
中的Mode
或许Size
。只有生成了子View的MeasureSpec,咱们才干够经过调用子View的measure办法丈量出子View的巨细。
那么MeasureSpec是怎样创立的呢?
SpecMode有三类,分别是UNSPECIFIED、EXACTLY、AT_MOST
三种。UNSPECIFIED不用管,是体系内部运用的。那么什么时分是EXACTLY,什么时分是AT_MOST呢?有些同学可能知道,它的取值跟LayoutParams
是有联系的。但需求留意的是,它不是完全由本身的LayoutParams决定的,LayoutParams需求和父容器一起才干决定View本身的SpecMode。当View是DecorView时,MeasureSpec依据窗口尺度和本身的LayoutParams就能承认:
# ViewRootImpl
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
...
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); // 注释1
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // 注释2
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
注释1与注释2处获取的便是窗口尺度与本身LayoutParams的尺度。当顶层View的MeasureSpec
承认后,在onMeasure
办法中就会对下一层的View进行丈量,获取子View的MeasureSpec。咱们能够看一下ViewGroup的measureChildWithMargins
办法,它在许多ViewGroup的onMeasure中都会被调用到:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 注释1
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width); // 注释2
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 注释3
}
咱们能够看到注释1处首要是获取到子View本身的LayoutParams
。然后再在注释2处依据父View的MeasureSpec
以及padding
、margin
,生成了子View的MeasureSpec
。终究再经过注释3处调用measure
丈量出了子View的长度。那么父View的MeasureSpec传进去有什么用呢?后边代码有点长,我直接写定论:
当父View的MeasureMode是EXACTLY时,子View的LayoutParams假如是MATCH_PARENT或许写死的值,子View的MeasureMode是EXACTLY;子View的LayoutParams假如是WRAP_CONTENT,子View的MeasureMode是AT_MOST。
当父View的MeasureMode是AT_MOST时,子View的LayoutParams只有是写死的值时,子View的MeasureMode才会是EXACTLY,不然这种状况都是AT_MOST。
有一个比较容易了解的办法是,AT_MOST
通常对应的LayoutParams是WRAP_CONTENT
。咱们能够想想,父View假如是WRAP_CONTENT
,子View即使是MATCH_CONTENT
,那子View还不是相当于尺度不承认吗?所以子View这种状况下的MeasureMode仍然是AT_MOST
。只有在尺度承认的状况下,View的MeasureMode才会是EXACTLY。假如这儿紊乱的同学,能够再好好揣摩一下。
至于padding和margin,咱们大约想一想就知道必定是在丈量长度时用到的。比方在计算子View长度时,必定是需求把它的padding和margin都去掉才干精确。
别的,咱们调查View的onMeasure办法,发现它供给了默认的完成:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
经过setMeasuredDimension(width, height)
设置了View的宽高。但这儿的getDefaultSize()是精确的吗?咱们能够看一下:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize; // 注释1
break;
}
return result;
}
注释1处咱们能够看到,当SpecMode为AT_MOST时,默认直接用的是specSize。所以这儿是有问题的,由于这个specSize代表父View的size,这样会形成LayoutParams为WRAP_CONTENT的效果是MATCH_PARENT的效果。所以许多官方的自定义View都是重写了onMeasure办法,自己去计算尺度。咱们在写自定义View时也需求特别留意这一点。
而ViewGroup是一个抽象类,它并没有完成View的onMeasure办法,那是由于每个ViewGroup的布局规则都不相同,自然丈量的办法也会不同,需求各个子类自己去完成onMeasure。
layout进程
layout
进程只需求知道,layout首要是父容器用来承认容易中子View方位的一个进程。当父容器承认方位后,在父容器的onLayout
中会遍历其一切子元素调用它们的layout
办法。而layout办法实践上便是承认四个极点坐标的方位。
咱们能够看到onLayout
与onMeasure
办法相似,View和ViewGroup都没有完成它,由于每个View布局办法不同,需求自己完成。但由于在承认坐标进程中需求用到长和宽,所以layout的次第排在measure的后边。
调查View#getWidth()
与View#getHeight()
办法:
public final int getWidth() {
return mRight - mLeft;
}
public final int getWidth() {
return mRight - mLeft;
}
能够看到实践上它们便是坐标之间做了减法,所以这两个办法要在onLayout办法之后才干获取到真正的值。也便是View的实践宽高。一般状况下measureWidth与width是会相等的,除非咱们特意重写了layout办法(layout办法与measure办法不相同,它是能够被重写的):
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r+11, b-11)
}
这样,实践宽高与丈量宽高就会不一致了。
自定义View
自定义View一共分为四种类型:
- 承继View
- 这种类型常见于要制作一些不规则的图形,需求重写它的
onDraw
办法。需求的留意的是,View的Padding特点在制作进程中默认是不起效果的,假如要使Padding特点起效果,就需求自己在onDraw
中获取Padding后制作。别的,onMeasure
时需求考虑wrap_content和padding的状况,上文也有说到过。
- 这种类型常见于要制作一些不规则的图形,需求重写它的
- 承继ViewGroup
- 这种类型比较少,一般用于完成自定义的布局。那就意味着要自定义布局规则,也便是自定义
onMeasure
、onLayout
。在onMeasure
中,也需求处理wrap_content和padding的状况。别的在onMeasure
和onLayout
中,还需求考虑padding与子元素margin共同效果的场景。
- 这种类型比较少,一般用于完成自定义的布局。那就意味着要自定义布局规则,也便是自定义
- 承继某个特定的View,比方TextView
- 这种类型一般用于扩展已有的某个View的功用,比较容易完成,不需求重写
onMeasure
或许onLayout
办法。
- 这种类型一般用于扩展已有的某个View的功用,比较容易完成,不需求重写
- 承继某个特定的ViewGroup,比方FrameLayout
- 这种类型也很常见,一般用于将几个View组合到一起,但相对第二种办法会简略许多,也不需求重写
onMeasure
或许onLayout
办法。
- 这种类型也很常见,一般用于将几个View组合到一起,但相对第二种办法会简略许多,也不需求重写
在自定义View中还需求额定留意的是,假如View中有额定开线程或许是动画的话,需求在合适的机遇(比方onDetachedFromWindow生命周期)进行回收或许暂停,不然容易引起内存泄漏。别的,假如涉及到嵌套滑动的话可能还需求处理滑动抵触,可参阅这篇文章:Android斩首行动——滑动抵触。
六、Window与WindowManager
Window相关类
Window是一个抽象类,它的详细完成是PhoneWindow
。PhoneWindow
在Activity#attach
办法中被创立:
#Activity
final void attach(Context context...) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback); // 注释1
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // 注释2
}
注释1处初始化了PhoneWindow
,注释2处给Window设置了WindowManager
。WindowManager
,顾名思义便是用来办理Window的,它承继了ViewManager
接口:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
从上面的代码咱们能够看出,办理Window,实践上便是办理View。context.getSystemService(Context.WINDOW_SERVICE)
办法终究获取的是WindowManager
的完成类WindowManagerImpl
,咱们能够调查WindowManagerImpl
中的这三个办法,如addView
:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
能够看到实践上addView
是由mGlobal
去完成的,WindowManagerImpl
只是桥接了一下。这个mGlobal
是WindowManagerGlobal
,是个单例,大局仅有:
#WindowManagerImpl
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
Window类型
Window类型详细为Window的Type
特点。Type
特点其实是一个int指,分为三大类型:
- 运用程序窗口
- 常见的Activity的Window就归于运用程序窗口,它的int值规模是1-99。
- 子窗口
- 子窗口代表着要依附于其它窗口才干存在,比方PopupWindow就归于一个子窗口,它的int值规模是1000-1999。
- 体系窗口
- Toast、音量条、输入法的窗口就归于体系窗口,int值规模是2000-2999。当咱们要创立一个体系窗口时,需求请求体系权限android.permission.SYSTEM_ALERT_WINDOW才干够。
一般来说,type特点值越大,那么Z-Order的排序就越靠前,窗口越挨近用户。
Window的操作
增加View
咱们连续上方的addView
代码,看一下这儿面的进程:
# WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params); // 注释1
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
// Only use the default token if we don't have a parent window.
if (mDefaultToken != null && mParentWindow == null) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// Only use the default token if we don't already have a token.
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
# WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display); // 注释2
view.setLayoutParams(wparams);
// 注释3
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 注释4
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
注释1调用了applyDefaultToken
办法,将token
放在了LayoutParams
中。咱们能够发现Window的许多办法中都有这个token,这个token究竟是什么,有什么效果呢?实践上这个token是一个IBinder
目标,是AMS创立Activity时就会创立的,用来仅有标识一个Activity,创立后WMS也会拿到一份储存起来。当AMS调用scheduleLauncherActivity
办法转回到APP进程时,会将这个token传到APP进程中。当咱们在APP进程对Window进行操作时,就会用到这个token。由于终究Window的操作是在WMS,所以咱们调用WMS办法时会将这个token传过去,WMS就会比对这个token和最开端存储的token,以此来找到这个Window是归于哪个Activity。、
再持续回来,咱们看注释2处WindowManagerGlobal#addView
办法中每次都会new一个ViewRootImpl
目标。ViewRootImpl
咱们很熟悉,上一节有讲到,它担任View的制作作业。这儿它不只担任View的制作,还担任与终究的WMS进行通讯。详细能够看到注释4处调用了ViewRootImpl#setView
办法,内部再调用IWindowSession#addToDisplay
办法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
...
}
IWindowSession
实践上是WMS的Session目标
在APP进程的署理,这样咱们的逻辑就到了WMS那边了,WMS会完成剩下的addView操作,包括为增加的窗口分配Surface,承认窗口显现次第,终究将Surface交给SurfaceFlinger
处理,组成到屏幕上进行显现。而且,addToDisplay
办法的第一个参数mWindow,是ViewRootImpl
的内部类W
,它是app进程的Binder完成类,WMS能够经过它调用app进程的办法。
别的咱们能够看到WindowManagerGlobal
在注释3处维护了三个列表,一个是View
,一个是ViewRootImpl
,一个是Params
布局参数,在更新Window/移除Window时会用到。
小结
增加View的进程可用下图归纳:
更新Window
更新Window的进程与增加的进程是相似的,所经过的类联系一模相同。首要的区别在WindowManagerGlobal
中需求从列表中获取到ViewRootImpl,并更新布局参数:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
ViewRootImpl会调用scheduleTraversals
办法从头制作页面,终究在performTraversals
办法中调用IWindowSession#relayout
办法更新Window,并从头触发View的三大制作流程。
Activity烘托
在第三节咱们讲了Activity的发动进程,但其实还没有完全讲完,由于Activity实践上是没有烘托出来的。那Activity是怎样烘托出来的呢?当然也是靠Window。
当界面可与用户进行交互时,AMS会调用ActivityThread
的handleResumeActivity
办法:
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); // 注释1
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); // 注释2
} else {
...
}
...
注释1处调用performResumeActivity
办法,内部会触发Activity的onResume
生命周期。注释2处用WindowManager#addView
办法将decorView制作到了Window上,这样整个Activity就烘托出来了。这也是Activity在onResume生命周期才会显现出来的原因。
Dialog、PopupWindow、Toast
终究,仍是觉得有必要搞明白Dialog、PopupWindow、Toast这三个东西究竟是什么玩意儿。毫无疑问,它们都是经过Window烘托的,但Dialog归于运用程序窗口,PopupWindow归于子窗口,Toast归于体系级窗口。
Dialog的创立时,跟Activity相同会创立一个PhoneWindow:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext); // 注释1
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
咱们看到注释1处Dialog初始化时是创立了自己的Window,所以它不依附于其它Window存在,不归于子窗口,而是一个运用程序窗口。
在Dialog#show()
时,就会用WindowManager将DecorView增加到窗口上,移除时同样是将DecorView从窗口上移除。有一个特别之处是,一般的dialog的context必须是Activity,不然show时会报错,由于增加窗口时需求校验Activity的token,除非咱们把这个dialog的window设置成体系窗口,就不需求了。
而PopupWindow为什么就归于子窗口呢?咱们能够查阅PopupWindow的源码:
# PopupWindow
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
mContext = contentView.getContext();
// 注释1
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
setContentView(contentView);
setWidth(width);
setHeight(height);
setFocusable(focusable);
}
public void showAtLocation(IBinder token, int gravity, int x, int y) {
...
final WindowManager.LayoutParams p = createPopupLayoutParams(token); // 注释2
preparePopup(p);
...
invokePopup(p);
}
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType; // 注释3
p.token = token; // 注释4
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
...
}
private void invokePopup(WindowManager.LayoutParams p) {
...
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManager.addView(decorView, p); // 注释5
...
}
在源码中,咱们能够看到PopupWindow中不会有任何新建Window的操作,由于它依赖的是别人的Window。在注释1处拿到WindowNManager,注释2处调用createPopupLayoutParams
办法给Window参数赋值。特别留意注释3和注释4处两个字段,type
字段即代表了窗口的类型,咱们能够看到mWindowLayoutType的值是WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
,这就代表了是子窗口。且注释4处的token
,不再是Activity的token了,而是show的时分传进来的View的token。
Toast也是相似,咱们能够看到它的源码中WindowParams.LayoutParams.type
的值是WindowManager.LayoutParams.TYPE_TOAST
,对应着体系窗口。详细内部机制这儿就不剖析了,感爱好的同学能够自行查阅。
七、Handler音讯机制
Handler音讯机制,最首要的效果便是在多线程的背景下,经过音讯机制,能够从子线程切换到主线程进行UI的更新操作,而且确保了线程安全。
首要介绍其间的首要成员以及他们之间的联系。
- Message
- 音讯,可用来存放数据,经过Message.obtain()可从缓存池中获取一个音讯目标。
- MessageQueue
- 用于存储音讯的行列。
- Looper
- loop办法会持续监听MessageQueue,当MessageQueue中有音讯时,将音讯拿出来进行分发。
- 一个线程只会有一个Looper,一个Looper对应一个MessageQueue,在Loope创立时,对应的MessageQueue就会创立。一个Handler也只能绑定一个Looper,可是一个Looper能够绑定多个Handler,Looper是以线程为单位的。
- Looper线程阻隔,是经过Threadlocal完成的。Looper下有个静态变量sThreadLocal,每个线程下调用Looper.myLooper()时便是从这个变量中拿,获取当前线程下的Looper。
- Looper分为子线程Looper与MainLooper。MainLooper在ActivityThread的
main方
法中就会经过Looper.prepareMainLooper()
办法准备好。它们的区别一个是能够quit的,一个是不能quit的。所谓的quit便是调用looper.quit
办法,实践上是在MessageQueue中进行quit,将其间的音讯给移除。quit又分是否安全quit。safequit下,会先将音讯行列中已有的音讯先发完再quit。
- Handler
- 用于发送Message与接纳Message,作为Message中的target。
- Message.Callback
- 当Message.callback存在时,Handler将接纳不到Message,而是直接回调到callback中。 Handler在post一个Runnable时,实践上便是发送一个音讯并将这个Runnable作为这个音讯的callback存在。
那整个音讯通讯的流程又是什么样的呢?咱们能够串起来说一下。
- 首要通讯准备阶段,
Looper.prepare()
给当前线程创立一个Looper,同时创立一个MessageQueue。 new一个Handler。 -
handler.post
或许handler.sendMessage
发送一个音讯,入队到MessageQueue。 - Looper将MessageQueue中的行列按优先级分发给Handler。
- Handler获取到Message,并依据之中的数据处理音讯。
在写事务的时分咱们常常会post/send
一个延时音讯,这个延时音讯是怎样完成的呢?每个Message都有一个when
字段,对应着它什么时分需求被分发。在入队到MessageQueue时,就会依据when
的先后进行摆放。当loop取音讯时,会判别这个音讯的分发时刻是否到了,假如还没到就先不分发,等时刻到了再分发。
所以在音讯机制中,并不是发一个音讯就能够确保它立刻会被处理的,由于机制内部就有优先级摆放。那怎样能够在咱们需求的时分进步音讯的优先级呢?咱们能够调用postAtFrontOfQueue
以及sendMessageAtFrontOfQueue
将音讯放到队头,其实是将这个音讯的when设置为0。或许,运用异步音讯与同步屏障。
异步音讯与同步屏障
异步音讯与同步屏障其实咱们开发时很少会用到,一般都是体系自己运用,同步屏障的接口也是hide的,咱们要调用只能反射调用。但由于面试很常见,这儿也顺带提一下。
异步音讯的创立咱们能够在创立Message时调用setAsynchronous
办法将Message设置为异步音讯,或许Handler创立时也能够传参选择是否为异步,假如是异步的话,Handler发送的一切音讯都为异步音讯。
那什么又是同步屏障呢?同步屏障的本质其实便是特别的Message,特别在于这个Message的Target不是Handler,而是null,以此与其它Message区分。咱们能够经过反射调用MessageQueue的postSyncBarrier
办法发送一个同步屏障,以及removeSyncBarrier
办法进行移除。
咱们能够将同步屏障与异步音讯结合,以此来进步异步音讯是被优先履行的。详细原理是MessageQueue在Loop取出Message时,会判别Message是否为同步屏障。若为同步屏障,则需求在行列中找到第一个异步音讯进行优先处理,而不是处理排在前面的同步音讯:
#MessageQueue
Message next() {
...
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous()); // 注释1
}
...
}
注释1处当发现msg.target
为null时,代表着这个音讯是同步屏障,就会去拿行列中第一个异步音讯。
上文中讲到过view的更新操作,其间在终究的制作阶段ViewRootImpl会经过requestLayout()
进行布局的更新,requestLayout()
内部又调用scheduleTraversals()
办法然后从头走一遍三大制作流程。
# ViewRootImpl
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 注释1
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //注释2
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
在这个办法中咱们没有看到三大制作流程的触发,而是在注释1处发了一个同步屏障,阻塞主线程音讯行列。同时在注释2处监听VSYNC信号。当VSYNC信号来时,Choreographer会发一个异步音讯,这个异步音讯在同步屏障的帮助下将会得到履行,而且触发监听回调。监听回调中ViewRootImpl将移除同步屏障,并调用performTraversals()
履行三大制作流程刷新UI。
总结
本篇文章一共介绍了七点Framework常识,分别是:体系发动流程、运用进程发动流程、Activity发动进程、Context、View的作业原理、Window与WindowManager、Handler音讯机制。这几点都是笔者目前以为Android事务开发需求掌握的常识点,它们有些是面试常问的,有些是平常开发或多或少会接触到的,甚至咱们做Hook与插件化时都会用到里面的常识(后边会专门出一篇文章讲Hook与插件化)。像SystemServer
进程的代码本章一概没提,由于笔者自己也不太会,就不献丑啦,而且平常的确也没用到过。当然,这篇文章会随时更新,随时添补常识的空白,哈哈~希望以上内容能够帮到我们!
文章不足之处,还望我们多多海涵,多多指点,先行谢过!
Framework源码在线阅览网址
androidxref.com/
cs.android.com/
参阅书籍
《Android开发艺术探索》——任玉刚
《Android进阶解密》——刘望舒