Window和WindowManager(二)

一、Window创建过程:
View不能单独存在,必须依附在Window上
如何创建一个Window窗口:
通过WindowManager创建,Window创建的具体实现是在WindowManagerService中, 而 WindowManager 与WindowManagerService 的交互是一个IPC过程。

1、Activity的Window创建
Activity的启动最终是由ActivityThead的performLaunchActivity()完成启动,通过类加载器创建Activity对象。
在performLaunchActivity()中调用activity的attach方法
#ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
   ......

      if (activity != null) {
         Context appContext = createBaseContextForActivity(r, activity);
         CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
         Configuration config = new Configuration(mCompatConfiguration);
         if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
               + r.activityInfo.name + " with config " + config);
         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);
   ......
}
(1)#Activity 
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) {
   attachBaseContext(context);

   mFragments.attachHost(null /*parent*/);

   mWindow = new PhoneWindow(this);
   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);
   }
   ......
}
创建Activity的Window对象PhoneWindow,设置回调接口。
Activity 实现了Window的Callback接口,所以window接收到外界状态改变会调用Activity中的方法。
常见的方法例如:onWndowFouseChanged、onAttachedToWindow、onDetachFromWindow、dispatchTouchEvent。
(2)setContentView
#Activity
public void setContentView(int layoutResID) {
	//调用Window的setContView方法
	getWindow().setContentView(layoutResID);
	initActionBar();
}
#PhoneWindow(Window实现类)
@Override
public void setContentView(int layoutResID) {
	if (mContentParent == null) {
		//创建DecorView
		installDecor();
	} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
		mContentParent .removeAllViews();
	}

	if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
		final Scene newScene = Scene.getSceneForLayout (mContentParent , layoutResID,
				getContext());
		transitionTo(newScene);
	} else {
		//将acvivity的视图添加到DecorView的mContentParent中
		mLayoutInflater .inflate(layoutResID, mContentParent);
	}
	mContentParent .requestApplyInsets();
	final Callback cb = getCallback();
	if (cb != null && !isDestroyed()) {
		cb.onContentChanged(); //通知Activity,通过onContentChanged方法
	}
}
DecorView是一个FrameLayout,是Activity的顶级view,包含标题栏和内部栏。
通过generateDecor()方法创建DecorView。
#PhoneWindow
protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}
PhoneWindow 在 installDecor方法中调用generateLayout加载具体的布局文件到DecorView中。
#PhoneWindow
protected ViewGroup generateLayout(DecorView decor) {
   ......
   View in = mLayoutInflater.inflate(layoutResource, null);
   decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
   mContentRoot = (ViewGroup) in;

   ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
   if (contentParent == null) {
      throw new RuntimeException("Window couldn't find content container view");
   }
   ......
}
  • 如果没有DecorView,则调用installDecor创建DecorView
  • 调用generateLayout,View添加到DecorView的mConentParent中
  • 回调Activity的onContentChanged的方法通知Activity视图已经改变
(因为Activity是实现了Window的Callback接口,当布局文件添加到DecorView上时,会回调Activity的onContentChanged方法)
总结:通过前三步DecorView初始化完成,Activity的布局文件添加到了DecorView中。
但是DecorView还没有被WindowManager正式添加到Window中
(3)在ActivityThread的handleResumeActivity中,调用Actiivty的onResume方法,调用Activity的makeVisible()。DecorView 完成添加和显示的过程。
#Activity
void makeVisible() {
    if (!mWindowAdded ) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());  //将DecorView添加到Window上
        mWindowAdded = true;
    }
    mDecor .setVisibility(View.VISIBLE);
}
Activity的Window创建过程:
.......ActivityThread(调用performLaunchActivity)—>Activity(调用attach,创建Window)—>Activity(调用setContentView,创建DecorView,将布局文件添加到DecorView上)—>ActivityThread(调用hanldeResumeActivity)—>Activity(调用onResume,makeVisible)—>调用WindowManager.addView将DecorView添加到Window上并显示

2、Dialog的Window的创建
(1)和Activity一样,先完成Window的创建,创建后的对象实际是PhoneWindow
#Dialog
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.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);

    mListenersHandler = new ListenersHandler(this);
}
(2)初始化DecorView,并将Dialog添加到DecorView中。
调用setContentView,内部也是调用PhoneWindow的setContentView方法完成。
public void setContentView(@LayoutRes int layoutResID) {
    mWindow.setContentView(layoutResID);
}
(3)在show方法中通过WindowManager将DecorView添加到Window中并显示。 (当Dialog被关闭时,通过WindowManager移除DecorView)
#Dialog
public void show() {
   .....

   try {
      mWindowManager.addView(mDecor, l);
      mShowing = true;

      sendShowMessage();
   } finally {
   }
}
#Dialog  
dismiss调用dismissDialog方法
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 {
        mWindowManager.removeViewImmediate(mDecor);
    } finally {
        if (mActionMode != null) {
            mActionMode.finish();
        }
        mDecor = null;
        mWindow.closeAllPanels();
        onStop();
        mShowing = false;

        sendDismissMessage();
    }
}
注:Dialog必须引用Activity的context,因为需要token,token只有Activity中拥有。系统Window不需要token

Dialog的Window创建过程:
Dialog(构造方法中创建PhoneWindow)—>Dialog(调用setContentView)—>PhoneWindow(调用setContentView,创建DecorView,将视图添加到DecorView中)—>Dialog(调用show方法,WindowManager.addView将DecorView添加到Window并显示)

3、Toast的window的创建
Toast属于系统Window,它内部的视图由两种方式指定:一种是系统默认的样式,另一种是通过setView方法来指定一个自定义的View。
在Toast的内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的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 {
        service.enqueueToast(pkg, tn, mDuration);
    } catch (RemoteException e) {
        // Empty
    }
}
取消
public void cancel() {
    mTN.hide();
    try {
        getService().cancelToast( mContext.getPackageName(), mTN);
    } catch (RemoteException e) {
        // Empty
    }
}
Toast的显示和隐藏是IPC过程,都需要NotificationManagerService(NMS)来实现,因为NMS运行在系统进程中,只能通过远程调用的方式显示和隐藏Toast。
在Toast和NMS进行IPC过程时,NMS会跨进程回调Toast中的TN类中的方法,TN类是一个Binder类,运行在Binder线程池中,所以需要通过Handler将其切换到当前发送Toast请求所在的线程,所以Toast无法在没有Looper的线程中弹出。
#NotificationManagerService
private final IBinder mService = new INotificationManager.Stub() {
                @Override
                public void enqueueToast(String pkg, ITransientNotification callback, int duration)
                {
                ……  
                                 synchronized (mToastQueue) {
                                                 int callingPid = Binder. getCallingPid();
                                                 long callingId = Binder. clearCallingIdentity();
                                                 try {
                                                                ToastRecord record;
                                                                 int index = indexOfToastLocked(pkg, callback);
                                                                 // If it's already in the queue, we update it in place, we don't
                                                                // move it to the end of the queue.
                                                                 if (index >= 0) {
                                                                                record = mToastQueue.get(index);
                                                                                record.update(duration);
                                                                } else {
                                                                                 // Limit the number of toasts that any given package except the android
                                                                                // package can enqueue.  Prevents DOS attacks and deals with leaks.
                                                                                 if (!isSystemToast) {
                                                                                                 int count = 0;
                                                                                                 final int N = mToastQueue.size();
                                                                                                 for ( int i = 0; i < N; i++) {
                                                                                                                 final ToastRecord r = mToastQueue.get(i);
                                                                                                                 if (r.pkg.equals(pkg)) {
                                                                                                                                count++;
                                                                                                                                 if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                                                                                                                                Slog.e( TAG, "Package has already posted " + count
                                                                                                                                                                                + " toasts. Not showing more. Package=" + pkg);
                                                                                                                                                 return;
                                                                                                                                }
                                                                                                                }
                                                                                                }
                                                                                }

                                                                                record = new ToastRecord(callingPid, pkg, callback, duration);
                                                                                mToastQueue.add(record);
                                                                                index = mToastQueue.size() - 1;
                                                                                keepProcessAliveLocked(callingPid);
                                                                }
                                                                 if (index == 0) {
                                                                                showNextToastLocked(); //显示当前的Toast
                                                                 }
                                                } finally {
                                                                Binder. restoreCallingIdentity(callingId);
                                                }
                                }

                                .....
                }

                @Override
                public void cancelToast(String pkg, ITransientNotification callback) {
                   .....
                                 synchronized (mToastQueue) {
                                                 long callingId = Binder. clearCallingIdentity();
                                                 try {
                                                                 int index = indexOfToastLocked(pkg, callback);
                                                                 if (index >= 0) {
                                                                                cancelToastLocked(index);
                                                                } else {
                                                                                Slog.w( TAG, "Toast already cancelled. pkg=" + pkg
                                                                                                                + " callback=" + callback);
                                                                }
                                                } finally {
                                                                Binder. restoreCallingIdentity(callingId);
                                                }
                                }
                }
(1)enquequeToast将Toast封装成ToastRecord,并放入mToastQueue的列队中。对于非系统应用来说,mToastQueue最多能同时存在50个ToastRecord。因为如果某个应用弹出太多的Toast会导致其他应用没有机会弹出Toast。
#NotificationManagerService
void showNextToastLocked() {
    ToastRecord record = mToastQueue.get(0);
    while (record != null) {
        if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
        try {
            record.callback.show();
            scheduleTimeoutLocked(record);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Object died trying to show notification " + record.callback
                    + " in package " + record.pkg);
            // remove it from the list and let the process die
            int index = mToastQueue.indexOf(record);
            if (index >= 0) {
                mToastQueue.remove(index);
            }
            keepProcessAliveLocked(record.pid);
            if (mToastQueue.size() > 0) {
                record = mToastQueue.get(0);
            } else {
                record = null;
            }
        }
    }
}
(2)NMS内部通过调用showNextToastLocked显示当前Toast。Toast由ToastRecord的callback完成,callback类是ITransientNotification,具体实现是Toast中的TN对象,是一个远程的Binder。所以最终被调用的TN会运行
在发起Toast应用的Binder线程池中。
(3)Toast显示完后,NMS调用scheduleTimeoutLocked发送延时消息,然后NotificationManagerService调用cancelToastLocked掩藏Toast,并从队列中删除。
Toast的隐藏也是通过ToastRecord的callback完成。也是一次IPC过程。
(4)Toast的显示和隐藏是通过Binder(TN)调用其中的show和hide方法,最后show和hide内部调用了TN中的handleShow和handleHide方法,将Toast视图添加到window上,或者在window上移除
#Toast
public void handleShow() {
   .....
      mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
      ......
      mWM.addView(mView, mParams);
   ......
}

public void handleHide() {
    if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
    if (mView != null) {
        // note: checking parent() just to make sure the view has
        // been added...  i have seen cases where we get here when
        // the view isn't yet added, so let's try not to crash.
        if (mView.getParent() != null) {
            if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
            mWM.removeView(mView);
        }

        mView = null;
    }
}

Toast的Window创建过程:

Toast(调用show)—>INotificationManager—>NotificationManagerService(调用enquequeToast,添加到队列中)—>NotificationManagerService(调用showNextToastLocked显示Toast)—>Toast.TN(跨进程调用show方法)—>Toast.TN(调用handleShow,将Toast添加到Window中)






















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值