Activity从创建到显示的整个过程

简介: 写在前面的话今天有点烦,有点烦。项目写的乱成团,改起需求真要完。此后当个加班狗,无钱无名心要宽。昨晚写到十一点,我都差点不相信这是我自己了。今天接着昨天的节奏来,准备写下关于Activity从创建到显示的整个过程。

写在前面的话

今天有点烦,有点烦。项目写的乱成团,改起需求真要完。此后当个加班狗,无钱无名心要宽。
昨晚写到十一点,我都差点不相信这是我自己了。


img_d023a979c36ee1c50ae1f83b28a6e828.png

今天接着昨天的节奏来,准备写下关于Activity从创建到显示的整个过程。


img_632b49059d24a0a5f0b75c5688829a3b.png

1. Activity的attach方法

之前分析过Activity的生命周期具体调用时机,我们知道Activity是通过反射创建出来的,之后会执行attach方法:

ActivityThread.java:
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);
Activity.java:
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window) {
    //把context赋值给mBase,可以通过getBaseContext获取到context
    attachBaseContext(context);
    
    mFragments.attachHost(null /*parent*/);
    //创建了一个窗口
    mWindow = new PhoneWindow(this, window);
    //这个好像是画中画的callback
    mWindow.setWindowControllerCallback(this);
    //设置callback,里面有各种事件包括键盘、触摸等事件的回调
    mWindow.setCallback(this);
    //设置窗口消失的回调
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    //与输入法有关的设置
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    //ui线程
    mUiThread = Thread.currentThread();
    //主线程
    mMainThread = aThread;
    //之前说到的小秘书
    mInstrumentation = instr;
    //binder
    mToken = token;
    mIdent = ident;
    mApplication = application;
    mIntent = intent;
    mReferrer = referrer;
    mComponent = intent.getComponent();
    mActivityInfo = info;
    mTitle = title;
    mParent = parent;
    mEmbeddedID = id;
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    ......
    //设置(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    //WindowManager
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
}

attach方法大部分都是在做成员变量的赋值操作,比如上下文,主线程和UI线程。里面比较重要的一点是对Window的创建,我们都知道Activity的layout是显示在窗口上面的,这个PhoneWindow就是我们的窗口。可以看下其构造方法:

PhoneWindow.java:
public PhoneWindow(Context context) {
    super(context);
        //初始化mLayoutInflater  layout加载器
    mLayoutInflater = LayoutInflater.from(context);
}

public PhoneWindow(Context context, Window preservedWindow) {
    this(context);
    
    mUseDecorContext = true;
    //传过来的preservedWindow为null
    if (preservedWindow != null) {
        ......
    }
    boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
            DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
    //是否支持画中画
    mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
            PackageManager.FEATURE_PICTURE_IN_PICTURE);
}

Window.java:
public Window(Context context) {
    //把上下文赋值,并设置默认的特征
    mContext = context;
    mFeatures = mLocalFeatures = getDefaultFeatures(context);
}

attach到这边基本上就结束了。


2. Activity的onCreate

按照上篇讲的,在attach完成后,会执行Activity的onCreate方法。

ActivityThread.java:
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);

if (customIntent != null) {
    activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
    activity.setTheme(theme);
}

activity.mCalled = false;
if (r.isPersistable()) {
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
    mInstrumentation.callActivityOnCreate(activity, r.state);
}
Activity.java:
protected void onCreate(@Nullable Bundle savedInstanceState) {
    if (mLastNonConfigurationInstances != null) {
        mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
    }
    if (mActivityInfo.parentActivityName != null) {
        if (mActionBar == null) {
            mEnableDefaultActionBarUp = true;
        } else {
            mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
        }
    }
    if (savedInstanceState != null) {
        Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
        mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.fragments : null);
    }
    //FragmentController分发Create事件
    mFragments.dispatchCreate();
        //application分发ActivityCreated事件
    getApplication().dispatchActivityCreated(this, savedInstanceState);
    if (mVoiceInteractor != null) {
        mVoiceInteractor.attachActivity(this);
    }
    mCalled = true;
}

从上面的onCreate中可以看到,这个过程并没有做太多的操作,只有当前create的事件分发。


3. Activity的onStart和onResume

在执行完Activity的onCreate方法就会执行Activity的onStart方法,onStart方法更简单。。。

protected void onStart() {
    mCalled = true;
    mFragments.doLoaderStart();
    getApplication().dispatchActivityStarted(this);
}

看完onStart,就可以猜出onResume做了什么。没错,就是:

protected void onResume() {
    getApplication().dispatchActivityResumed(this);
    mActivityTransitionState.onResume(this, isTopOfTask());
    mCalled = true;
}

看到这里,其实会有一点点懵逼的。前人总是说,onStart代表着Activity可见了,onResume代表着可以交互了。但是到这里了,我才发现如何把View添加到窗口上的?什么时候添加的?绘制的时间呢?古人诚但欺我。


img_7a6503c6de4a64fd8ee8d43581680612.png

到了这里,希望得到的答案并没有出现,所以继续分析。


4. 真正的添加过程

真正添加的过程其实在执行完performResumeActivity这个方法后,系统根据该Activity是否要显示来设置页面是否需要添加到窗口上,其中最主要的是wm.addView(decor, l)这句代码,通过WindowManager将view和LayoutParams添加到窗口上,并且显示出来(这部分后面会有分析)。

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ......
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;
        final int forwardBit = isForward ?
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
        //将要显示
        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            try {
                willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                        a.getActivityToken());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        //这个条件a.mFinished false  r.window == null willBeVisible = true
        if (r.window == null && !a.mFinished && willBeVisible) {
            //这个r.activity.getWindow()就是我们attach中创建的PhoneWindow
            r.window = r.activity.getWindow();
            //获得DecorView,这里暂时理解为一个View吧,后面还会有分析的
            View decor = r.window.getDecorView();
            //DecorView设为INVISIBLE
            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;
            //false(在创建这个对象的时候没有赋值)
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            //mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(com.android.internal.R.styleable.Window_windowNoDisplay, false);
            //如果Activity没有设置NoDisplay,则这个mVisibleFromClient变量是true
            //mWindowAdded这个值是默认的,只有被add后才会变为true
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }

        } else if (!willBeVisible) {
            r.hideForNow = true;
        }
        
        cleanUpPendingRemoveWindows(r, false /* force */);

        if (!r.activity.mFinished && willBeVisible
                && r.activity.mDecor != null && !r.hideForNow) {
            if (r.newConfig != null) {
                performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
                r.newConfig = null;
            }
            
            WindowManager.LayoutParams l = r.window.getAttributes();
            ......
            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            //这里又执行了makeVisible,因为我们在上面已经add了,mWindowAdded这个变量为true,所以不会进行多次add。
            //最后decorView设置为显示
            //void makeVisible() {
            //  if (!mWindowAdded) {
            //      ViewManager wm = getWindowManager();
            //      wm.addView(mDecor, getWindow().getAttributes());
            //      mWindowAdded = true;
            //  }
            //  mDecor.setVisibility(View.VISIBLE);
            //}
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        }

        if (!r.onlyLocalRequest) {
            r.nextIdle = mNewActivities;
            mNewActivities = r;
            //这两天才了解到这个addIdleHandler是指空闲时处理的消息,MessageQueue有专门的接口MessageQueue.IdleHandler
            //这里之前说道会执行前个页面的onStop
            Looper.myQueue().addIdleHandler(new Idler());
        }
        r.onlyLocalRequest = false;

        // Tell the activity manager we have resumed.
        if (reallyResume) {
            try {
                ActivityManagerNative.getDefault().activityResumed(token);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }

    } else {
        //出现问题就finish
        try {
            ActivityManagerNative.getDefault()
                .finishActivity(token, Activity.RESULT_CANCELED, null,
                        Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
}

5. 大致要总结下

从上面的分析过程中,我们可以知道Activity在创建的过程中,onCreate、onStart以及onResume其实都没有关于对页面显示的操作,真正显示页面是在onResmue之后,WindowManager将decorView添加后进行绘制(后面应该会将)。
今天也恰巧看到了一篇讲解关于MessageQueue.IdleHandler的文章,这个IdleHandler会在线程空闲的时候,指定一个操作。使用这个IdleHandler对于某些延时操作,但又不清楚页面是否真正绘制完成有奇效。


6. 写在后面的话

这些天讲了从Android启动到HomeActivity的启动,又从HomeActivity启动过程开始分析Activity的生命周期,接着这篇讲解了Activity从创建到显示的过程,但是并没有完全完成。这里只讲到了执行这些方法会将页面显示,但是具体如何显示没有说明。接下来应该将的就是这个页面如何绘制并展示出来。就这样。


img_5473fab044a555227fe3bc76f4958c0b.png
目录
相关文章
|
Android开发
【Android 返回堆栈管理】打印 Android 中当前运行的 Activity 任务栈信息 | Activity 任务栈信息分析 | Activity 在相同 Stack 中的不同 Task(一)
【Android 返回堆栈管理】打印 Android 中当前运行的 Activity 任务栈信息 | Activity 任务栈信息分析 | Activity 在相同 Stack 中的不同 Task(一)
1057 0
【Android 返回堆栈管理】打印 Android 中当前运行的 Activity 任务栈信息 | Activity 任务栈信息分析 | Activity 在相同 Stack 中的不同 Task(一)
|
6月前
|
Java Android开发
Activity的加载过程
Activity的加载过程
30 1
|
Android开发
Android中保存当前按钮的状态 按back键返回之后再次进入没有改变
Android中保存当前按钮的状态 按back键返回之后再次进入没有改变
66 0
|
Java API Kotlin
教你用更好的方式在Activity或Fragment之间传递以及返回数据
教你用更好的方式在Activity或Fragment之间传递以及返回数据
317 0
教你用更好的方式在Activity或Fragment之间传递以及返回数据
|
Android开发
【Android 返回堆栈管理】打印 Android 中当前运行的 Activity 任务栈信息 | Activity 任务栈信息分析 | Activity 在相同 Stack 中的不同 Task(二)
【Android 返回堆栈管理】打印 Android 中当前运行的 Activity 任务栈信息 | Activity 任务栈信息分析 | Activity 在相同 Stack 中的不同 Task(二)
307 0
【Android 返回堆栈管理】打印 Android 中当前运行的 Activity 任务栈信息 | Activity 任务栈信息分析 | Activity 在相同 Stack 中的不同 Task(二)
|
XML 数据格式 容器
Activity布局加载流程
Activity布局加载流程 一、布局加载流程 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.
1153 0
|
缓存 前端开发 Java
Activity显示到Window的过程
写在前面的话 今天看到一个新闻,关于虾米音乐的程序员写的注释。 好像图都绿了,突然间想起在我曾经老大的影响下,我的Log输出已经变成了fxxk。
991 0