Android 探索Dialog创建时token报错

参考书籍:<Android 内核剖析>

  • 创建Dialog时不能用Application的Context,为什么呢?
基于AOSP Version:2.3分析
  • 先看下Dialog创建到显示整体流程
// Dialog.java
public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener {
    // 构造方法
    public Dialog(Context context, int theme) {
        mContext = new ContextThemeWrapper(context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);
        // 当context是Activity对象的时候, getSystemService()获取的对象类型为Activity里面的Window.LocalWindowManager对象变量.
        // 当context是Application对象的时候, getSystemService()获取的对象类型为WindowManagerImpl,并且它是个单例.
        // Window.LocalWindowManager对象能够使用Activity中PhoneWindow对象的mAppToken变量,将它赋值给WindowManager.LayoutParams对象的token属性
        // WindowManagerImpl对象addView()方法根本就没有检查token这一逻辑,它也没有什么token变量.
        // 这是最本质的原因.
        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        // 创建PhoneWindow对象
        // 这一步会创建WindowManager.LayoutParams对象, 该对象的type默认变量值为应用窗口类型.
        Window w = PolicyManager.makeNewWindow(mContext);
        mWindow = w;
        ...
        // 为PhoneWindow对象创建mWindowManager变量.
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mUiThread = Thread.currentThread();
        mListenersHandler = new ListenersHandler(this);
    }
    // 为Dialog创建好PhoneWindow之后,这一步就可以为PhoneWindow创建mDecorView对象了.
    // 这里和Activity创建mDecorView是一样的,区别不大.
    public void setContentView(int layoutResID) {
        mWindow.setContentView(layoutResID);
    }
    // 以上准备工作做好之后, 就可以将Dialog展示出来了.
    public void show() {    
        ...    
        onStart();
        // 获取Dialog的mDecorView
        mDecor = mWindow.getDecorView();    
        // 获取PhoneWindow的WindowManager.LayoutParams对象
        WindowManager.LayoutParams l = mWindow.getAttributes();
        ...
        try {
            // 开始展示
            mWindowManager.addView(mDecor, l);
            mShowing = true;    
            sendShowMessage();
        } finally {
        }
    }
}
  • 为何传入Activity获取的是Window.LocalWindowManager对象?
// Activity.java
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks {
    @Override
    public Object getSystemService(String name) {
        // 这里Activity实现了Context中的getSystemService()方法
        // 然后如果name=="window",则返回Activity的变量mWindowManager.
        // 这个变量应该都清楚,它在Activity中的attach()方法中由Window创建的.
        ...
        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }
}
  • 为何传入Application获取的是WindowManagerImpl对象?
// 这个先来探索下Application中的mBase他是啥?
// 从Application创建说起
// ActivityThread.java
public final class ActivityThread {
    private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        try {
            // 从这里继续向下看
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            ...
        }
        ...
    }
}
// LoadedApk.java
final class LoadedApk {
    public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
        Application app = null;
        // 这里是获取清单文件中application节点中的name属性,一般这个属性是自己写的MyApplication.java类.
        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            // 如果application节点没有配置name属性, 那么就用系统自己的Application.java类
            appClass = "android.app.Application";
        }
        try {
            java.lang.ClassLoader cl = getClassLoader();
            // appContext其实就是Application中的mBase对象了,可以看到是ContextImpl类型
            ContextImpl appContext = new ContextImpl();
            appContext.init(this, null, mActivityThread);
            // 继续向下看
            app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
           ...
        }
        ...
        return app;
    }
}
// Instrumentation.java
public class Instrumentation {
    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        return newApplication(cl.loadClass(className), context);
    }

    static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        // 创建了一个Application对象
        Application app = (Application)clazz.newInstance();
        // context变量是 makeApplication()中创建的ContextImpl类型对象
        // 看看attach()方法
        app.attach(context);
        return app;
    }
}
// Application.java
public class Application extends ContextWrapper implements ComponentCallbacks {
    /* package */ final void attach(Context context) {
        // 再看下它
        attachBaseContext(context);
    }
}
// ContextWrapper.java
public class ContextWrapper extends Context {
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        // base变量也就是ContextImpl类型对象,用Application父类中的mBase变量存储.
        mBase = base;
    }
}
// 再回头看getSystemService()方法
// Dialog.java
public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener {
    public Dialog(Context context, int theme) {
        ...
        // 这里如果传入的是Application的话,看看它获取的是什么类型的对象.
        // 会调用Application父类ContextWrapper中的方法
        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        ...
    }
}
// ContextWrapper.java
public class ContextWrapper extends Context {
    Context mBase;
    @Override
    public Object getSystemService(String name) {
        // mBase是ContextImpl对象
        // 接着向下看
        return mBase.getSystemService(name);
    }
}
// ContextImpl.java
class ContextImpl extends Context {
    @Override
    public Object getSystemService(String name) {
        if (WINDOW_SERVICE.equals(name)) {
            // 终于看到了,如果传入的是Application,那么Dialog获取到的mWindowManager类型为WindowManagerImpl.
            return WindowManagerImpl.getDefault();
        } 
        ...
        return null;
    }
}
  • 使用Window.LocalWindowManager对象添加mDecor会做哪些事情呢?
// 首先明确一点这里的Window.LocalWindowManager对象是Activity中的变量,它在Activity对应的PhoneWindow对象的父类Window中存在.
// 
public abstract class Window {
    private class LocalWindowManager implements WindowManager {
        public final void addView(View view, ViewGroup.LayoutParams params) {
            // Dialog创建WindowManager.LayoutParams类型对象的时候,它的type变量默认值是应用窗口类型.
            WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params;
            CharSequence curTitle = wp.getTitle();
            if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                if (wp.token == null) {
                    View decor = peekDecorView();
                    if (decor != null) {
                        wp.token = decor.getWindowToken();
                    }
                }
                ...
            } else {
                if (wp.token == null) {
                    // wp是在Dialog构造方法中创建PhoneWindow时候创建的,它是没有token值的
                    // 这里mContainer一般是为null, 如果Activity有父类,比如GroupActivity的话,这个就是不为null
                    // mAppToken这个值就是当执行Activity.attach()方法时候, 会创建一个WindowPhone,这个token最终来源其实是AMS中的ActivityClient中的token值.
                    wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
                }
           }
           // 最后添加窗口
           // LocalWindowManager中的mWindowManager对象是由Activity中的attach()方法调用得来的
           // 它的类型为WindowManagerImpl.
           // 到这里就走到 WindowManagerImpl中的add() 方法中了
           mWindowManager.addView(view, params);
        }
    }
}
  • 使用WindowManagerImpl对象添加mDecor会做哪些事情呢?
    • 它与上面的唯一区别就是没有对WindowManager.LayoutParams类型对象做类型检查,所以也不会为该对象的type属性赋值.
基于AOSP Version:6.0分析

6.0的源码中, 有一小点的改动.不过还是一样的原因报错, 没有为WindowManager.LayoutParams类型对象的token赋值.

  • 通过Activity获取到的mWindowManager对象情况.
// 首先查看Activity中mWindowManager是如何创建的
// Activity.java
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2,Window.OnWindowDismissedCallback {
    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) {
        ...
        // 注意看这里,首先通过context对象获取一个WindowManager对象,
        // 这里的context对象类型为ContextImpl类型,
        // ContextImpl.getSystemService()返回的对象是类型是WindowManagerImpl.下面有分析.
        // Window.setWindowManager()会创建一个WindowManagerImpl对象,
        // 将Activity对应的Window保存到WindowManagerImpl对象的parentWindow变量中,
        // 然后Activity对应的Window对象中的mWindowManager对象指的就是新创建的WindowManagerImpl对象.
        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());
        }
        // 获取WindowManagerImpl类型的mWindowManager对象
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }
}
// Window.java
public abstract class Window {
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        // 这里可以看到将Activity对应的Window对象传入到WindowManagerImpl中了.
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
}
// ActivityThread.java
public final class ActivityThread {
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            ...
            if (activity != null) {
                // 这里是创建Context地方
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                ...
                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);
            }
        }
        return activity;
    }
    private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
        int displayId = Display.DEFAULT_DISPLAY;
        // 从这里可以看出,context的类型为ContextImpl.
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, displayId, r.overrideConfig);
        ...
        return baseContext;
    }        
}
// ContextImpl.java
class ContextImpl extends Context {
    // 返回ContextImpl对象
    static ContextImpl createActivityContext(ActivityThread mainThread, LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        return new ContextImpl(null, mainThread, packageInfo, null, null, false, null, overrideConfiguration, displayId);
    }
    // 如果使用ContextImpl.getSystemService()返回的对象是?
    // 还得看SystemServiceRegistry类
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
}
// SystemServiceRegistry.java
final class SystemServiceRegistry {
    static {
        ...
        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx.getDisplay());
            }});
        ...
    }
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }
    // 这里可以很明晰得看出通过Activity中context调用getSystemService()最终获取到的mWindowManager类型为WindowManagerImpl.
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
    // 这里可以看出来,执行Window.setWindowManager()方法时候,会重新创建一个WindowManagerImpl对象,
    // 而WindowManagerImpl对象中的parentWindow变量此时正是当前Activity所对应的Window对象.
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }
}
  • 通过Application获取到的mWindowManager对象情况.
    1. 我们得首先知道Application中的Context由来,其实这个步骤和2.3源码中一样的.他们最终获取的都是一个ContextImpl类型的context对象.
    2. 对于ContextImpl类型的context对象,通过它最终获取的mWindowManager对象类型是WindowManagerImpl.但是它与通过Activity中的mWindowManager对象有很重要的不同点是:
      • Activity中的mWindowManager对象中的parentWindow变量是Activity对应的Window对象.
      • 通过Application获取的mWindowManager对象中的parentWindow变量为null.
  • 上面分析了两个mWindowManager对象的区别,现在来分析他们如果执行addView()会有什么不同
public final class WindowManagerImpl implements WindowManager {
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        // mGlobal类型为WindowManagerGlobal,它是一个全局变量
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
}
public final class WindowManagerGlobal {
    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        // 最大的不同在这里
        // 当使用Activity中的mWindowManager时候,parentWindow是不为null
        // 当使用Application获取到mWindowManager时,它的parentWindow其实为null
        if (parentWindow != null) {
            // parentWindow一直在强调它就是Activity对应的Window对象
            // 看看Window中这个方法做了什么.
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        ...
    }
}
// Window.java
public abstract class Window {  
    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            // 首先Dialog的Type默认是应用窗口类型, 不是子窗口类型, 所以不会走这里
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
            ...
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
            // 也不是系统类型
            if (curTitle == null || curTitle.length() == 0) {
                String title = "Sys" + Integer.toString(wp.type);
                if (mAppName != null) {
                    title += ":" + mAppName;
                }
                wp.setTitle(title);
            }
        } else {
            // 最后,终于到了这一步
            // 此时的WindowManager.LayoutParams对象的type是为null的
            if (wp.token == null) {
                // 这个mAppToken其实就是由Activity.attach()中执行Window.setWindowManager()时后所传入的token
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            ... 
        }
    }

}
总结

文章中分析了两个版本的源码,可以看出它们最终原因都是一样的,不同版本源码可能代码调用逻辑不同,本质还是没有怎么改变.
很多的博客也探讨过这个问题,有的说两种Context中token一个为空一个不为空,有的说WindowManagerImpl对象中token一个为空一个不为空,这两个结论放在2.3或者6.0源码中其实都是错误的.
2.3源码中是因为LocalWindowManager对象能够获取到Activity对应Window对象中的mAppToken.
6.0源码中是因为WindowManagerImpl对象中parentWindow变量一个为空,一个是Activity对应的Window对象.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值