源码学习_12Dialog
2018-05-02 本文已影响21人
冉桓彬
一、参考文章:
为什么Dialog不能用Application的Context
使用Dialog时需要注意的问题
创建Dialog所需的上下文为什么必须是Activity?
二、问题:
- 1、为什么Dialog不能传ApplicationContext, 以及如何解决?
三、demo:
public class Activity {
public void method() {
DialogTest dialogTest = new DialogTest(getApplicationContext());
dialogTest.show();
}
}
public class DialogTest extends Dialog {
DialogTest(@NonNull Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_test);
}
}
- 上面代码会GG, 错误提示如下:
四、源码分析:
4.1 Dialog构造函数:
public class Dialog {
public Dialog(@NonNull Context context) {
this(context, 0, true);
}
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
mContext = new ContextThemeWrapper(context, themeResId);
} else {...}
/**
* 1. WindowManager指向WindowManagerImpl;
* 2. Context有两种情况: Context instanceof Application; Context instanceof ActivityContext,
* 结合这两种模式构建WindowManagerImpl的方式可能不同<4.2>;
* 3. 结合模块<4.2>可知, 如果Context instanceof Application, 构建一个Application_WML, 如果
* Context instanceof ActivityContext, 则返回Activity_WML;
*/
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
/**
* 1. ContextThemeWrapper持有我们外部传进来的Context, 可能指向ApplicationContext/ActivityContext;
* 2. PhoneWindow只有ContextThemeWrapper的引用;
*/
w.setWindowManager(mWindowManager, null, null);
}
}
public class PhoneWindow {
public Window(Context context) {
mContext = context;
...
}
public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
setWindowManager(wm, appToken, appName, false);
}
/**
* 1. 结合上面传来的参数---> Dialog构造函数会触发PhoneWindow被初始化, 然后通过setWindowManager
* 将PhoneWindow与WindowManager进行关联;
* 2. 此时PhoneWindow内部的mAppToken默认为null;
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
mAppToken = appToken;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
}
public final class WindowManagerImpl implements WindowManager {
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
}
4.2 WindowManagerImpl构建:
public class Application {
@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
}
public class ContextImpl {
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
}
public class Activity {
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
...
}
return super.getSystemService(name);
}
}
4.3 Dialog.show:
public void show() {
/**
* mShowing默认为false, 调用show()方法之后, mShowing = true, dismiss被触发时, mShowing = false;
*/
if (mShowing) {
return;
}
mCanceled = false;
/**
* 1. 默认为false, 触发dispatchOnCreate之后, mCreated = true;
* 2. dispatchOnCreate内部会触发onCreate的执行;
*/
if (!mCreated) {
dispatchOnCreate(null);
} else {
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
/**
* 采用模板模式, 控制Dialog的生命周期;
*/
onStart();
/**
* 这里采用的方式其实有些类似Activity, 我们在onCreate中执行setContentView方法, 然后
* 该方法会构造一个DecorView出来, 这里的mWindow指向PhoneWindow, mDecor指向DecorView;
*/
mDecor = mWindow.getDecorView();
/**
* 构建LayoutParams; 模块<4.4>
* 结合模块<4.4>可知, Dialog窗口类型默认为应用级别;
*/
WindowManager.LayoutParams l = mWindow.getAttributes();
/**
* 1. mWindowManager实际指向WindowManagerImpl;模块<4.5>
* 2. onCreate内部通过setContentView构建DecorView树, 然后通过这里将DecorView与WindowManager
* 进行绑定, 并绘制在界面上;
*/
mWindowManager.addView(mDecor, l);
mShowing = true;
...
}
4.4 PhoneWindow.getAttributes:
public class PhoneWindow {
private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
public final WindowManager.LayoutParams getAttributes() {
return mWindowAttributes;
}
}
public class WindowManager.LayoutParams {
public LayoutParams() {
/**
* 创建Dialog会走这个方法, 也就是说Dialog的窗口类型默认为应用级别;
*/
type = TYPE_APPLICATION;
}
}
4.5 WindowManagerImpl.addView:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
/**
* 对token进行赋值操作; 模块<4.6>
*/
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
...
}
/**
* 1. 将DecorView与ViewRootImpl进行绑定, 然后显示在WindowManager中;
* 2. 如果构建Dialog时传入Application会抛出上文所述异常, 而异常的原因就出在setView里面;模块<4.7>
*/
root.setView(view, wparams, panelParentView);
}
4.6 WindowManagerImpl.applyDefaultToken:
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
/**
* 结合模块<4.1> ~ <4.2>可知, 针对传入的Context不同, WMI有两种情况:
* 1. 如果传入的Context属于Application, 则直接跳过<//1--->>处的if语句;
* 2. 如果传入的Context属于Activity, 则根据Activity启动流程可知, if内语句被赋值, 所以
* wparams.token不为空;
*/
if (mDefaultToken != null && mParentWindow == null) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//1--->
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
4.7 ViewRootImpl.setView:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
mView = view;
attrs = mWindowAttributes;
/**
* 1. 通过查看ViewRootImpl构造函数可知, mWindowSession实际指向Session;模块<4.8>
* 2. 结合模块<4.8> 可知mWindowSession.addToDisplay最终调用WMS的addWindow方法;模块<4.9>
*/
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
...
if (res < WindowManagerGlobal.ADD_OKAY) {
...
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
}
}
}
}
4.8 Session.addToDisplay:
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
/**
* mService指向的是WMS; 模块<4.9>
*/
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
4.9 WMS.addWindow:
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
int[] appOp = new int[1];
/**
* 1. 校验机制太长了, checkAddPermission触发窗口类型和权限机制的校验; 模块<4.10>;
* 2. 结合模块<4.10>可知, res有以下几种类型:
* (1) WindowManagerGlobal.ADD_OKAY: 如果窗口类型为应用级别, 或者系统级别且对应权限已经
* 进行了申请(包括动态申请/在清单文件中申请);
* (2) WindowManagerGlobal.ADD_PERMISSION_DENIED: 窗口级别为需要申请权限的系统级别, 但
* 是没有进行权限申请时;
* 3. 如果type为WindowManagerGlobal.ADD_PERMISSION_DENIED, 则直接返回该值, 反之在<//1>处
* 继续进行具体的判断;
*/
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
//1--->
/**
* 结合模块<7>可知, type类型从大方面来说有三种: 系统级别、应用级别, 子窗口级别;
*/
final int type = attrs.type;
synchronized(mWindowMap) {
...
boolean addToken = false;
/**
* token默认来自何处? 这里在回到Dialog的构造函数中;从Dialog构造函数<4.10>中了解到构造Dialog时,
* token默认被置为null, 然后如果Context instanceof ActivityContext时, taken会被赋值是因为
* Dialog使用的是Activity的WML, 持有的token实际为Activity的token;
*/
WindowToken token = mTokenMap.get(attrs.token);
/**
* 1. 能执行到这里, 说明res == WindowManagerGlobal.ADD_OKAY成立, 而该等式成立分三种情况:
* (1) 窗口类型为应用级别;
* (2) 窗口类型为系统级别, 且该系统级别不需要申请权限;
* (3) 窗口类型为系统级别, 且该系统级别需要申请权限, 而且确实已经申请了权限;
* 2. 如果context属于Activity, 则token!=null, 如果context属于Application,
* 则taken == null;
*/
if (token == null) {
/**
* 1. 窗口类型为应用级别, 且token == null, 此时抛出该异常, 响应模块<4.7>
*/
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
...
/**
* 1. 执行到这里说明: token == null, 且窗口级别为系统级别;
* 2. 然后初始化token;
*/
token = new WindowToken(this, attrs.token, -1, false);
addToken = true;
}
else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {...}
else if (type == TYPE_INPUT_METHOD) {...}
else if (type == TYPE_VOICE_INTERACTION) {...}
else if (type == TYPE_WALLPAPER) {...}
else if (type == TYPE_DREAM) {...}
else if (type == TYPE_ACCESSIBILITY_OVERLAY) {...}
else if (token.appWindowToken != null) {...}
...
/**
* 再次对type进行校验; 模块<4.11>
*/
res = mPolicy.prepareAddWindowLw(win, attrs);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
res = WindowManagerGlobal.ADD_OKAY;
}
return res;
}
4.10 PhoneWindowManager.checkAddPermission:
@Override
public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
/**
* 1. 结合上文---> attrs = PhoneWindow.getAttributes();模块<4.7>
* 2. 结合模块<4.7>可知, Dialog窗口类型默认为应用级别, 即TYPE_APPLICATION, 直接在<//1--->>
* 返回WindowManagerGlobal.ADD_OKAY;
*/
int type = attrs.type;
//1--->
/**
* 如果这里if()内部执行语句为false, 也就是说当前窗口类型为系统级别或者是子窗口级别, 跳转至<//2--->>
*/
if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) {
return WindowManagerGlobal.ADD_OKAY;
}
//2--->
String permission = null;
/**
* 1. 如果窗口为系统级别, 在switch内部判断是否对具体系统级别申请了对应的权限;
* 2. 如果权限申请成功, 则返回type为WindowManagerGlobal.ADD_OKAY;
* 3. 如果权限申请失败, 则返回type为WindowManagerGlobal.ADD_PERMISSION_DENIED;
*/
switch (type) {
case TYPE_TOAST:
outAppOp[0] = AppOpsManager.OP_TOAST_WINDOW;
break;
case TYPE_DREAM:
case TYPE_INPUT_METHOD:
case TYPE_WALLPAPER:
case TYPE_PRIVATE_PRESENTATION:
case TYPE_VOICE_INTERACTION:
case TYPE_ACCESSIBILITY_OVERLAY:
break;
case TYPE_PHONE:
case TYPE_PRIORITY_PHONE:
case TYPE_SYSTEM_ALERT:
case TYPE_SYSTEM_ERROR:
case TYPE_SYSTEM_OVERLAY:
permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
break;
default:
permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
}
if (permission != null) {
if (permission == android.Manifest.permission.SYSTEM_ALERT_WINDOW) {
final int callingUid = Binder.getCallingUid();
if (callingUid == Process.SYSTEM_UID) {
return WindowManagerGlobal.ADD_OKAY;
}
final int mode = mAppOpsManager.checkOp(outAppOp[0], callingUid, attrs.packageName);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
case AppOpsManager.MODE_IGNORED:
return WindowManagerGlobal.ADD_OKAY;
case AppOpsManager.MODE_ERRORED:
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
default:
if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) {
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
} else {
return WindowManagerGlobal.ADD_OKAY;
}
}
}
if (mContext.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
}
}
return WindowManagerGlobal.ADD_OKAY;
}
4.11 PhoneWindowManager.prepareAddWindowLw:
@Override
public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
switch (attrs.type) {
case TYPE_STATUS_BAR:...break;
case TYPE_NAVIGATION_BAR:...break;
case TYPE_NAVIGATION_BAR_PANEL:
case TYPE_STATUS_BAR_PANEL:
case TYPE_STATUS_BAR_SUB_PANEL:
case TYPE_VOICE_INTERACTION_STARTING:...break;
case TYPE_KEYGUARD_SCRIM:...break;
}
return WindowManagerGlobal.ADD_OKAY;
}
五、如何避免在Dialog中使用ApplicationContext抛异常的问题:
5.1 改变Dialog窗口的级别类型:
在show方法执行之前;
public class Window {
public void setType(int type) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.type = type;
dispatchWindowAttributesChanged(attrs);
}
}
DialogTest(@NonNull Context context) {
super(context);
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
5.2 申请权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
或者直接将type类型改为Toast类型;
ublic class DialogTest extends Dialog {
DialogTest(@NonNull Context context) {
super(context);
getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST);
}
}