理解Window和WindowManager(二)

三、Activity的Window创建过程

1. Window的创建

Activty的启动最终会在ActivtyThread的performLaunchActivty()中完成。

ActivtiyThread # performLaunchActivity()

在performLaunchActivty()中:

  1. 反射创建了一个Activty实例。
  2. 调用它的attach()方法关联运行过程中依赖的上下文变量。
  3. 对它进行初始化。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // .....
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        // 获取类加载器
        java.lang.ClassLoader cl = appContext.getClassLoader();
        // 反射获取一个Activty实例
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        // ......
    try {
        // ......
        if (activity != null) {
            // ......
            // 调用attach()给activty关联变量
            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);
            // ......
    return activity;
}

能new的为什么要用反射?

反射机制增加程序的灵活性,避免将程序写死到代码里,实例化一个对象,不使用反射,如果想变成实例化其他类,那么必须修改源代码,并重新编译。使用反射,可以将类描述可以写到配置文件中,这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。

Activity # attach()

在这个方法中,会创建Activty视图内容所需的Window。

  1. 创建Window对象。
  2. 设置Window有关监听。
  3. 初始化Window。
  4. 赋值Activty的各种参数。
final void attach(/*一系列的参数,太长了......*/) {
    // ......
    
    // 创建Window对象
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    // 传入Window有关的监听,Activty实现的
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    
    // ......初始化Window中的部分内容
    // ......赋值各种参数
    
    // 设置WMS
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        // 看是否有父Window依赖
        mWindow.setContainer(mParent.getWindow());
    }
    // 赋值参数
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
    mWindow.setColorMode(info.colorMode);
}

2. 初始视图

从setContentView()开始,这是为Activty设置视图的起点。

Activity # setContentView()

直接调用了Window的setContentView()。

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
PhoneWindow # setContentView()

在这里会调用installDecor()去创建DecorView,在完成后,mContentParent加载传入的Activity布局。

  1. 创造初始DecorView。
  2. mContentParent加载参数布局。
  3. 调用监听接口。
@Override
public void setContentView(int layoutResID) {
    // 初始化DecorView
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        // 如果已经创建过DecorView结构,已经有了content,就移除
        mContentParent.removeAllViews();
    }
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        // ......
    } else {
        // mContentParent中加载传入的布局资源
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    // ......
    if (cb != null && !isDestroyed()) {
        // 回调通知Activity视图已经改变
        cb.onContentChanged();
    }
    // ......
}
PhoneWindow # installDecor()

在这个方法里,先创建了DecorView,再创建了DecorView的子结构mContentParent,这个就是ViewGroup就是用来装setContentView()布局的。

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        // 如果还没有DecorView,调用generateDecor()创建
        mDecor = generateDecor(-1);
        // ......
    } else {
        // 如果有DecorView对象了,就关联此Window
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // 赋值mContentParent
        mContentParent = generateLayout(mDecor);
        
        // 初始化布局......
        
    }
}
PhoneWindow # generateDecor()

在这个方法中

  1. 创建了DecorView对象,传入了该Window对象。
  2. 在DecorView的构造方法中将DecorView和Window绑定了。
protected DecorView generateDecor(int featureId) {
    // ......创建context参数
    return new DecorView(context, featureId, this, getAttributes());
}
PhoneWindow # generateLayout()
  1. DecorView加载布局。
  2. contentParent的findView后返回给mContentParent。在前面讲到的PhoneWindow的setContentView()中加载布局。
protected ViewGroup generateLayout(DecorView decor) {
    
    // 根据Window的R.styleable等参数设置Window的flag、params、layoutResource......
    
    mDecor.startChanging();
    // DecorView加载布局
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
    // ID_ANDROID_CONTENT = com.android.internal.R.id.content
    // setContentView()布局加载的ViewGroup
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    
    // ......
    
    mDecor.finishChanging();
    return contentParent;
}

3. Window的添加

到这时,Window创建完毕,试图也初始化完毕了,但是DecorView还没有正式的被WindowManagerService添加到Window中。

ActivityThread # handleResumeActivity()

在handleResumeActivty()中,会调用Activity的makeVisible()。

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        // ......
        r.activity.makeVisible();
        // ......
}
Activity # makeVisible()
  1. 获取WMS对象,这个对象在attach()时就已经初始化了。
  2. 调用addView()添加Window。
  3. 设置DecorView可见。
void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

四、Dialog的Window创建过程

Dialog的Window的创建过程和Activity非常相似。

1. Window的创建

Dialog # Dialog()

在构造方法中获取了WMS,创建了Window,并初始化。

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    // .....
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    final Window w = new PhoneWindow(mContext);
    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);
}

创建Dialog对象时传入的context不能是Application的context,一般的Dialog需要应用token,应用token是Activity才有的,所以需要传入Activity的context,否则就会报错。

但是如果Dialog的Window设置成系统级别则不需要应用token。

dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR);

2. 初始视图

Dialog # setContentView()

同样是调用了PhoneWindow的setContentView去加载布局。

public void setContentView(@LayoutRes int layoutResID) {
    mWindow.setContentView(layoutResID);
}
PhoneWindow # setContentView()

这个方法在Activity已经分析过了,和之前的一样,创建并初始化DecorView,mContentParent加载参数的布局。

3. Window的添加

Dialog # show()

在Dialog的show()中添加Window,调用WindowManager的addView()。

public void show() {
    // ......
    // 监听通知
    onStart();
    mDecor = mWindow.getDecorView();
    // ......
    WindowManager.LayoutParams l = mWindow.getAttributes();
    // ......
    // 添加Window
    mWindowManager.addView(mDecor, l);
    mShowing = true;
    sendShowMessage();
}

4. Window的移除

调用Dialog的dismiss()时,会移除Window。

Dialog # dismiss()
@Override
public void dismiss() {
    if (Looper.myLooper() == mHandler.getLooper()) {
        dismissDialog();
    } else {
        mHandler.post(mDismissAction);
    }
}
Dialog # dismissDialog()

在这里会调用WindowManager的removeViewImmediate()同步移除这个Dialog的Window。

void dismissDialog() {
    if (mDecor == null || !mShowing) {
        return;
    }
    if (mWindow.isDestroyed()) {
        Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
        return;
    }
    try {
        // 移除Window
        mWindowManager.removeViewImmediate(mDecor);
    } finally {
        if (mActionMode != null) {
            mActionMode.finish();
        }
        // 置空回收
        mDecor = null;
        mWindow.closeAllPanels();
        onStop();
        mShowing = false;
        sendDismissMessage();
    }
}

五、Toast的Window创建过程

  • Toast属于系统Window。
  • 内部会有两类IPC过程(除了和WMS的)
    • Toast访问NotificationManagerService的过程。
    • NotificationManagerService回调Toast里的TN接口。
  • 因为Toast的定时取消功能使用了Handler,所以Toast不能在没有Looper的线程中弹出。

从最熟悉show()开始。

1. 调用NMS

Toast # show()

IPC调用NMS,传入了回调TN和Toast时长。

public void show() {
    if (mNextView == null) {
        throw new RuntimeException("setView must have been called");
    }
    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;
    try {
        // 远程调用NMS
        service.enqueueToast(pkg, tn, mDuration);
    } catch (RemoteException e) {
        // Empty
    }
}

在NotificationManagerService中,mService是INotificationManager.Stub的匿名对象,实现了Binder接口定义的方法,包括show()中调用的enqueueToast()。

2. NMS处理Toast

NotificationManagerService的mService # enqueueToast()
  1. 查看是否已经存在可以复用的Toast了。
    • 存在就更新Toast内容和存在时间。
    • 不存在
      • 创建一个Binder对象,调用WMS添加。
      • 重新封装一个ToastRecord,放入mToastQueue。
  2. 调用showNextToastLocked()显示mToastQueue中的第一个Toast。
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration) {
    // ......
    synchronized (mToastQueue) {
        // ......
        try {
            // Toast的封装对象
            ToastRecord record;
            int index;
            if (!isSystemToast) {
                // 如果不是系统应用的Toast,就遍历ToastRecord集合。
                // 看是否存在来自于同一个包的Toast。如果存在就返回对应下标,没有就返回-1。
                index = indexOfToastPackageLocked(pkg);
            } else {
                // 如果是系统Toast,除了对比包名还要对比是不是同一个callback,返回下标。
                index = indexOfToastLocked(pkg, callback);
            }
            if (index >= 0) {
                // 如果找到了,就直接跟新内容和时间。
                record = mToastQueue.get(index);
                record.update(duration);
                record.update(callback);
            } else {
                // 创建一个token,显式加入WMS,显式加入就需要显式移除,在后面会看到。
                Binder token = new Binder();
                mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                // 如果不存在,就新创一个ToastRecord,并加入集合。
                record = new ToastRecord(callingPid, pkg, callback, duration, token);
                mToastQueue.add(record);
                index = mToastQueue.size() - 1;
            }
            keepProcessAliveIfNeededLocked(callingPid);
            // 如果添加前还没有ToastRecord,就开始显示
            if (index == 0) {
                showNextToastLocked();
            }
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }
}

3. 回调TN,显示Toast

NotificationManagerService # showNextToastLocked()
  1. TN的show()被远程调用。
  2. 发送一个Toast时长的延时消息。
  3. 处理显示失败的情况,继续尝试显示队列中的下一个Toast。
void showNextToastLocked() {
    // 第一个Toast
    ToastRecord record = mToastQueue.get(0);
    while (record != null) {
        if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
        try {
            // 调用TN接口的show(),在应用的Binder线程中执行
            record.callback.show(record.token);
            // 发送一个延时消息,取决于Toast显示时长
            scheduleTimeoutLocked(record);
            return;
        } catch (RemoteException e) {
            // ......
            // 处理IPC调用失败,显示失败的情况
            int index = mToastQueue.indexOf(record);
            if (index >= 0) {
                // 如果显示失败的Record还在队列中就移除
                mToastQueue.remove(index);
            }
            keepProcessAliveIfNeededLocked(record.pid);
            // 如果队列中还有Record,就继续处理下一个
            if (mToastQueue.size() > 0) {
                record = mToastQueue.get(0);
            } else {
                record = null;
            }
        }
    }
}
Toast$TN # show()

这个方法是通过NMS以跨进程的方式调用的,运行在应用的Binder线程。因为需要切换线程,所以使用了handler。

@Override
public void show(IBinder windowToken) {
    if (localLOGV) Log.v(TAG, "SHOW: " + this);
    mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}

调用了handleShow方法,参数token是在NotificationManagerService()中构建的和WMS通信的Binder对象。

@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case SHOW: {
            IBinder token = (IBinder) msg.obj;
            handleShow(token);
            break;
        }
        // ......
    }
}
Toast$TN # handleShow()
  1. 判断是否有提交了取消的消息,有就直接返回。
  2. 移除现在显示的。
  3. 初始化WindowManager。
  4. 如果此WM之前有添加过,就先移除之前添加的。
  5. 调用WM的addView()添加。

mNextView是在makeText()中加载的布局。如果是自定义布局的Toast,就在setView()中赋值的。

public void handleShow(IBinder windowToken) {
    // 如果已经有取消的消息了,就不要去处理显示了
    if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
        return;
    }
    // 如果现在显示的不是将要显示的
    if (mView != mNextView) {
        // 移除现在显示的
        handleHide();
        // 赋值
        mView = mNextView;
        Context context = mView.getContext().getApplicationContext();
        String packageName = mView.getContext().getOpPackageName();
        if (context == null) {
            context = mView.getContext();
        }
        // 获取WindowManager
        mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        // 处理Window的参数
        final Configuration config = mView.getContext().getResources().getConfiguration();
        final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
        mParams.gravity = gravity;
        // ......
        mParams.token = windowToken;
        // 如果之前存在过,就先把之前的移除
        if (mView.getParent() != null) {
            mWM.removeView(mView);
        }
        try {
            // 添加
            mWM.addView(mView, mParams);
            trySendAccessibilityEvent();
        } catch (WindowManager.BadTokenException e) {
            /* ignore */
        }
    }
}

4. 处理后事

NotificationManagerService # scheduleTimeoutLocked()

再来看执行完显示工作后的发送延时消息的方法。先是移除了存在的record的消息,然后延时发送了MESSAGE_TIMEOUT消息。

private void scheduleTimeoutLocked(ToastRecord r) {
    // 移除handler中有关此record的所有消息
    mHandler.removeCallbacksAndMessages(r);
    // 获取一个MESSAGE_TIMEOUT消息
    Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    // 延时时间为Toast显示时长
    long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
    // 延时发送
    mHandler.sendMessageDelayed(m, delay);
}

MESSAGE_TIMEOUT是什么,调用了handleTimeout()。

@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case MESSAGE_TIMEOUT:
            handleTimeout((ToastRecord)msg.obj);
            break;
        // ......
    }
}
NotificationficationManagerService # handleTimeout()

执行了cancelToastLocked(),看起来是去删除Toast的操作。

private void handleTimeout(ToastRecord record) {
    if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
    synchronized (mToastQueue) {
        int index = indexOfToastLocked(record.pkg, record.callback);
        if (index >= 0) {
            cancelToastLocked(index);
        }
    }
}
Notification # cancelToastLocked()
  1. 远程调用TN的hide()。
  2. 从队列中删除此ToastRecord。
  3. 调用WMS移除token。
  4. 如果队列中还有Toast,调用showNextToastLocked()显示下一个Toast。
void cancelToastLocked(int index) {
    ToastRecord record = mToastQueue.get(index);
    try {
        // 远程调用TN的hide()
        record.callback.hide();
    } catch (RemoteException e) {
        // ......
    }
    // 将这个record从Toast队列中移除
    ToastRecord lastToast = mToastQueue.remove(index);
    // 调用WMS显式移除Window的token,这个token是在enqueueToast()中添加的
    mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);
    keepProcessAliveIfNeededLocked(record.pid);
    // 如果Toast队列中还有Record,就调用继续执行下一个
    if (mToastQueue.size() > 0) {
        showNextToastLocked();
    }
}
Toast$TN # hide()

和TN的show()一样,也使用handler发送了一个消息,调用了handleHide()。

@Override
public void hide() {
    if (localLOGV) Log.v(TAG, "HIDE: " + this);
    mHandler.obtainMessage(HIDE).sendToTarget();
}
Toast # handleHide()

调用WM的removeViewImmediate()。

public void handleHide() {
    if (mView != null) {
        // 如果这个当前Toast的view确实被添加了
        if (mView.getParent() != null) {
            // 调用WindowManager的removeViewImmediate()同步移除Window
            mWM.removeViewImmediate(mView);
        }
        mView = null;
    }
}

你可能感兴趣的:(理解Window和WindowManager(二))