前言

信任做运用层事务开发的同学,都跟我相同,对Framework”深恶痛绝“。确实如此,假如素日里都在做运用层的开发,那么基本上咱们很少会去碰Framework的常识。但生活所迫,面试总是逃不过这一关的,所以作为一名合格的打工人,咱们仍是必须得具备一些Framework的基本常识。

网上有许多文章或浅或深地讲了Framework,有许多源码我们必定也都看过不止一两次,但过一段时刻就忘记了。我这篇文章将会从运用层开发的视角,罗列下我以为需求掌握的Framework基本常识。为了更便利我们去回想与巩固,我更多的是从流程上进行解说,而不会对源码进行十分深入的解读,文章会比较长,我们能够作为一个常识小册,依据目录针对性地进行浏览(基于Android 8.0/9.0)。

那话不多说,咱们冲!

一、体系发动流程

Zygote进程

Zygote进程是十分重要的一个进程,它担任Android体系中虚拟机(DVM或许ART)的创立、运用进程的创立以及SystemServer进程的创立。

体系在开机后会发动init进程,init进程进而会fork出Zygote进程。Zygote进程在发动时,首要做了这么几件作业:

  1. 创立虚拟机。当Zygote以fork本身的办法去创立运用进程和SystemServer进程时,它们就能拿到虚拟机的一个副本。
  2. 作为Socket服务端监听AMS恳求创立进程
  3. 发动SystemServer进程

SystemServer进程

SystemServer进程是咱们学习Framework中最重要的一个体系级进程,咱们熟知的许多服务(AMS、WMS、PMS等)都归于它供给的一个服务,是与咱们APP进程通讯最频繁的进程。

SystemServer进程在发动时,首要做了这么几件作业:

  1. 发动Binder线程池,为进程间通讯做准备
  2. 发动各种体系服务,比方AMS、WMS、PMS

Launcher进程

Launcher进程实践上便是咱们的桌面进程,熟悉它的同学都知道一页桌面本质上便是一个Grid类型的RecylerView,那它是怎样创立的呢?

这便是体系发动的终究一步,当SystemServer进程发动了AMS进程后,AMS进程会发动Launcher进程。Launcher进程经过与PMS进程通讯,获取到机器上一切的安装包信息后,将数据(APP图标、APP名称等)烘托到桌面上。

小结:

体系发动流程能够用下图归纳:

Android斩首行动——应用层开发Framework必知必会

二、运用进程发动流程

前面咱们讲了SystemServer进程与桌面进程是如何发动的,等它们发动好了之后,就能够从桌面发动咱们的运用进程了。

在桌面点击APP图标后,Launcher进程将会向AMS恳求创立APP的发动页面。AMS识别到APP进程不存在之后,就会用Socket的办法向Zygote进程恳求创立APP进程。Zygote经过fork的办法创立APP进程之后,会反射调用android.app.ActivityThreadmain()办法,初始化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办法又会一向调用到ApplicationThreadbindApplication办法,终究一向到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也会被发动

小结:

运用进程发动流程能够用下图归纳:

Android斩首行动——应用层开发Framework必知必会

三、Activity发动进程

上一节咱们现已将APP进程成功发动,但咱们的页面还没有起来,这一节咱们就讲一下Activity是如何发动的。

让咱们回想一下,上一节最开端是Launcher进程恳求AMS创立APP的发动页面,那么Launcher进程的桌面实践上也是一个Activtiy,它发动咱们的发动页面,也是调用的Activity#startActivity()办法。一层层进去后,咱们能够发现,实践上是调用的InstrumentationexecStartActivity()办法:

#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生命周期的调用:

Android斩首行动——应用层开发Framework必知必会

但它也不是真正的履行者,它只是包装了一下,为什么要这样包装呢?个人了解是由于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进程的ApplicationThreadApplicationThread归于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的逻辑其实大差不差,我们有爱好能够自行看一下源码。

小结

发动页面发动进程中各进程的交互联系:

Android斩首行动——应用层开发Framework必知必会
发动页面发动进程可用下图归纳:

Android斩首行动——应用层开发Framework必知必会

四、Context

APP中究竟有多少个Context呢?答案是Activity的数量+Service的数量+1,1是指Application。

Android斩首行动——应用层开发Framework必知必会

如上图所示,ContextImplContextWrapper都承继于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以及paddingmargin,生成了子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办法实践上便是承认四个极点坐标的方位

咱们能够看到onLayoutonMeasure办法相似,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
    • 这种类型比较少,一般用于完成自定义的布局。那就意味着要自定义布局规则,也便是自定义onMeasureonLayout。在onMeasure中,也需求处理wrap_content和padding的状况。别的在onMeasureonLayout中,还需求考虑padding与子元素margin共同效果的场景。
  • 承继某个特定的View,比方TextView
    • 这种类型一般用于扩展已有的某个View的功用,比较容易完成,不需求重写onMeasure或许onLayout办法。
  • 承继某个特定的ViewGroup,比方FrameLayout
    • 这种类型也很常见,一般用于将几个View组合到一起,但相对第二种办法会简略许多,也不需求重写onMeasure或许onLayout办法。

在自定义View中还需求额定留意的是,假如View中有额定开线程或许是动画的话,需求在合适的机遇(比方onDetachedFromWindow生命周期)进行回收或许暂停,不然容易引起内存泄漏。别的,假如涉及到嵌套滑动的话可能还需求处理滑动抵触,可参阅这篇文章:Android斩首行动——滑动抵触。

六、Window与WindowManager

Window相关类

Window是一个抽象类,它的详细完成是PhoneWindowPhoneWindowActivity#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设置了WindowManagerWindowManager,顾名思义便是用来办理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只是桥接了一下。这个mGlobalWindowManagerGlobal,是个单例,大局仅有:

#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的进程可用下图归纳:

Android斩首行动——应用层开发Framework必知必会

更新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会调用ActivityThreadhandleResumeActivity办法:

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存在。

那整个音讯通讯的流程又是什么样的呢?咱们能够串起来说一下。

  1. 首要通讯准备阶段,Looper.prepare()给当前线程创立一个Looper,同时创立一个MessageQueue。 new一个Handler。
  2. handler.post或许handler.sendMessage发送一个音讯,入队到MessageQueue。
  3. Looper将MessageQueue中的行列按优先级分发给Handler。
  4. 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进阶解密》——刘望舒