为啥一定要Activity的context才可以启动Dialog相关源码千里马带你站在高技术视角看问题

背景问题导出

hi,粉丝朋友们:
大家好!近期又有学员问了一个问题,这个问题其实做应用同学肯定都有遇到过哈,问题如下:
为啥Dialog创建时候一定强调要使用Activity的Context,而不可以使用全局Application的context呢?这块可以细细带着分析一下吗?
其实这个确实是个好问题哈,平时大部分同学我确定也只是知道个上面的结论,死记硬背下来了,但是作为学了千里马的wms/ams专题课程后,这个问题相对来说应该就不难了哈。
千里马的wms/ams专题课程点击这里
需要同学可以+威:androidframework007
在这里插入图片描述

针对这个Dialog的剖析我们将按如下流程来进行剖析:

在这里插入图片描述

wms层面进行dumpsys分析Dialog

对于WMS课程已经比较熟悉了同学,针对这类的窗口调研肯定是需要先使用dumpsys相关命令来看看这个Dialog的情况,下面以图库这个界面为例子进行分析:
在这里插入图片描述

这里有个Dialog,那么通过dumpsys activity containers查看一下层级结构输出:

 #3 Task=462 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 ActivityRecord{265c566 u0 com.android.gallery3d/.app.GalleryActivity} t462} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
          #1 372dff com.android.gallery3d/com.android.gallery3d.app.GalleryActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
          #0 d924826 com.android.gallery3d/com.android.gallery3d.app.GalleryActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

明显可以看到图库这个ActivityRecord下面居然挂载了两个WindowState
那么这里基本上可以判断Dialog其实也是有自己独立的WindowState,但是他属于ActivityRecord下面的节点,而且Dialog的WidnowState覆盖在Activity本身的WindowState的上面。
上面信息还不够,继续dumpsys window windows看看window的情况:

//Dialog的WindowState
Window #10 Window{372dff u0 com.android.gallery3d/com.android.gallery3d.app.GalleryActivity}:
    mDisplayId=0 rootTaskId=462 mSession=Session{
    
    812e805 1834:u0a10083} mClient=android.os.BinderProxy@8bce11e
    mOwnerUid=10083 showForAllUsers=false package=com.android.gallery3d appop=NONE
    mAttrs={
    
    (0,0)(wrapxwrap) gr=CENTER sim={
    
    adjust=pan forwardNavigation} ty=APPLICATION fmt=TRANSPARENT wanim=0x10302fb
      fl=DIM_BEHIND ALT_FOCUSABLE_IM SPLIT_TOUCH HARDWARE_ACCELERATED
      pfl=USE_BLAST INSET_PARENT_FRAME_BY_IME
      bhv=DEFAULT
      fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}
    Requested w=1366 h=443 mLayoutSeq=260
    mBaseLayer=21000 mSubLayer=0    mToken=ActivityRecord{
    
    265c566 u0 com.android.gallery3d/.app.GalleryActivity} t462}
    mActivityRecord=ActivityRecord{
    
    265c566 u0 com.android.gallery3d/.app.GalleryActivity} t462}
    mAppDied=false    drawnStateEvaluated=true    mightAffectAllDrawn=true
    mViewVisibility=0x0 mHaveFrame=true mObscured=false
    mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]
    mFullConfiguration={
    
    1.0 310mcc260mnc [en_US] ldltr sw411dp w411dp h797dp 560dpi nrml long port finger qwerty/v/v dpad/v winConfig={
    
     mBounds=Rect(0, 0 - 1440, 2960) mAppBounds=Rect(0, 0 - 1440, 2876) mMaxBounds=Rect(0, 0 - 1440, 2960) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0}


//Activity的WindowState
  Window #12 Window{d924826 u0 com.android.gallery3d/com.android.gallery3d.app.GalleryActivity}:
    mDisplayId=0 rootTaskId=462 mSession=Session{
    
    812e805 1834:u0a10083} mClient=android.os.BinderProxy@6deff81
    mOwnerUid=10083 showForAllUsers=false package=com.android.gallery3d appop=NONE
    mAttrs={
    
    (0,0)(fillxfill) sim={
    
    adjust=pan forwardNavigation} ty=BASE_APPLICATION fmt=TRANSLUCENT wanim=0x10302fa
      fl=LAYOUT_IN_SCREEN LAYOUT_INSET_DECOR SPLIT_TOUCH HARDWARE_ACCELERATED
      pfl=NO_MOVE_ANIMATION FORCE_DRAW_STATUS_BAR_BACKGROUND USE_BLAST FIT_INSETS_CONTROLLED
      bhv=DEFAULT
      fitSides=}
    Requested w=1440 h=2960 mLayoutSeq=260
    mBaseLayer=21000 mSubLayer=0    mToken=ActivityRecord{
    
    265c566 u0 com.android.gallery3d/.app.GalleryActivity} t462}
    mActivityRecord=ActivityRecord{
    
    265c566 u0 com.android.gallery3d/.app.GalleryActivity} t462}
    mAppDied=false    drawnStateEvaluated=true    mightAffectAllDrawn=true
    mViewVisibility=0x0 mHaveFrame=true mObscured=false
    mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]
    mFullConfiguration={
    
    1.0 310mcc260mnc [en_US] ldltr sw411dp w411dp h797dp 560dpi nrml long port finger qwerty/v/v dpad/v winConfig={
    
     mBounds=Rect(0, 0 - 1440, 2960) mAppBounds=Rect(0, 0 - 1440, 2876) mMaxBounds=Rect(0, 0 - 1440, 2960) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0}
  

经过上面的dumpsys信息可以得出以下结论差异:

1 Dialog的WindowState在Activity的WindowState上面,所以Dialog盖在上面
2 Dialog的window type是APPLICATION,但是Activity的是BASE_APPLICATION
3 他们有一个共同的mToken=ActivityRecord{265c566 u0 com.android.gallery3d/.app.GalleryActivity} t462}

所以这里有了以上信息就可以得出如下结论:

1、Dialog属于一个单独一个Window窗口,和Activity是独立的,二者在wms端都有独立的WindowState
2、Dilaog会和Activity有共同的token,有了这个一样token加上type为APPLICATION即可以决定二者都要被挂载到ActivityRecord下面

上面就是从wms层面看待这个Dialog情况,有了以上的结论后,我们再来看Dialog代码的具体实现部分,那就会有明确目标了,因为你已经从wms原理上剖析了Dialog情况,看源码时候大部分都要向这个结论上靠,那样整个上下流程我们就贯通了。
那么针对Dialog,就可以重点抓token的设置,和type设置即可以

我们wms课程学习时候都知道,一般要创建一个全局窗口一般要以下几个步骤:

1、创建一个RootView,这个RootView就是真正显示的内容部分
2、会调用WindowManager的addView方法,addView方法第一个参数就是上面RootView,第二个参数就是LayoutParams
真正与wms沟通最要的就是这个LayoutParams参数可以设置对应的type和token,也就是和我们前面说的wms结论联系上了

应用Framework层面Dialog的源码分析

先来看看要显示一个Dialog的最简单代码:

void showDialog() {
    
    
         Dialog d = new Dialog(this);
         d.setTitle("Dialog Title");
         d.setContentView(R.layout.window);
         d.show();
     }

可以看到Dialog需要进行new,而且需要传递Context参数,这里来重点看看Dialog的构造源码:
Dialog的构造方法有好几个,最后都会调用到如下代码

frameworks/base/core/java/android/app/Dialog.java


Dialog(@UiContext @NonNull Context context, @StyleRes int themeResId,
            boolean createContextThemeWrapper) {
    
    
        i     //省略部分
		//通过context获取WinowManager,注意这里非常关键哈
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		//直接new PhoneWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        //省略部分
        //调用PhoneWindow的setWindowManager来给自己设置合适的WindowManager
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
    }

这里来看看context.getSystemService(Context.WINDOW_SERVICE)最后其实会调用到Activity的
getSystemService,这里直接就使用了缓存的mWindowManager

@Override
public Object getSystemService(@ServiceName @NonNull String name) {
    
    
    if (getBaseContext() == null) {
    
    
        throw new IllegalStateException(
                "System services not available to Activities before onCreate()");
    }

    if (WINDOW_SERVICE.equals(name)) {
    
    
        return mWindowManager;
    } else if (SEARCH_SERVICE.equals(name)) {
    
    
        ensureSearchManager();
        return mSearchManager;
    }
    return super.getSystemService(name);
}

那么Activity的mWindowManager来自哪里,注意哈,这里我们就知道Dialog使用其实Activity的mWindowManager。那么这个Activity的在哪里赋值?在Activity的attach方法

  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, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
    
    
       

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
                
                }

再接下来就是PhoneWindow的setWindowManager

 public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
    
    
        mAppToken = appToken;//设置token到了windowmanager
        mAppName = appName;
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

这里又调用了一个createLocalWindowManager这里其实可以认为是Activity的WindowManager要创建一个本地的windowmanager

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    
    
        return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
    }
   private WindowManagerImpl(Context context, Window parentWindow,
            @Nullable IBinder windowContextToken) {
    
    
        mContext = context;
        mParentWindow = parentWindow;
        mWindowContextToken = windowContextToken;
    }

这里的WindowManagerImpl构造又一个变量非常关键parentWindow变量,一般parentWindow就是自己窗口的PhoneWindow
即这里有个结论:WindowManager的parentWindow对应就是自己PhoneWindow

PhoneWindow其实就是继承Window的,主要有个LayoutParams参数要注意
frameworks/base/core/java/android/view/Window.java

 private final WindowManager.LayoutParams mWindowAttributes =
        new WindowManager.LayoutParams();

来看看对应的LayoutParamas:

  public LayoutParams() {
    
    
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            type = TYPE_APPLICATION;//这里初始化就是TYPE_APPLICATION
            format = PixelFormat.OPAQUE;
        }

上面绕了一圈,其实总结如下:
1、Dialog中的mWindowManager其实是Activiyt的,因为context是Activity
2、Dialog也有new PhoneWindow,即它将来也会成为一独立的窗口,这个时候type默认就是TYPE_APPLICATION

那么接下来关注重点肯定是WidnowManager的addView了,看看是否还有对LayoutParams进行相关的改变:

在Dialog第一次调用show方法时候才会进行addView

    public void show() {
    
    
     //省略

        mWindowManager.addView(mDecor, l);//注意这里mWindowManager是Activity的哈
     
  //省略
    }

这里addView会一直调用到
frameworks/base/core/java/android/view/WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
    
    
       
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
    
    //因为是Activity的WindowManager这里的parentWindow就是activity的PhoneWindow,故不等于null
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
    
    
           
        }
//省略

            if (windowlessSession == null) {
    
    
                root = new ViewRootImpl(view.getContext(), display);
            } else {
    
    
                root = new ViewRootImpl(view.getContext(), display,
                        windowlessSession);
            }

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
    
    
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
    
    
      //省略
        }
    }


下面重点分析一下 adjustLayoutParamsForSubWindow(wparams)

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
    
    
        CharSequence curTitle = wp.getTitle();
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
    
    
                //省略
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
    
    
              //省略
        } else {
    
    //TYPE_APPLICATION会进入这里哈
            if (wp.token == null) {
    
    //前面Dialog没有对token又过赋值为null,进入这里
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;//这里会是的mAppToken,它和Activity的一样,所以就是这样把Dialog的token和Activity的token搞成一样的
            }
            if ((curTitle == null || curTitle.length() == 0)
                    && mAppName != null) {
    
    
                wp.setTitle(mAppName);
            }
        }
        //省略
    }


上面的这个方法就是核心关键,靠它把token给赋值上了,这样在wms端就有了把Dialog和Activity搞到一起重要依据了。

总结:

应用层面的Dialog就暂时分析到这里,我们分析的思路都是基于我们已经有了wms的较为深入的理解了,我们即可以站在更高的视角看问题,分析代码才有目的性,不会动不动太多代码,根本没办法准确分析到位,没有目的性,很有可能那就是你分析了很久代码,看了很多很多blog,你依然没办法抓到重点,你依然没办法串起来知识点。

猜你喜欢

转载自blog.csdn.net/learnframework/article/details/133001825
今日推荐