Dialog的View是如何绘制出来的--Dialog的Window(view)的创建过程

上一篇博客我们介绍了Activity的Window创建过程及最后如何展示出来的,这一篇我们接着分析Dialog的Window创建及展示。

Dialog在我们的开发中也算是一个特别常用的组件了, 比如AlertDialog, ProgressDialog、自定义Dialog等等, 他们都是Dialog类的子类, 所以这里我们就直接分析Dialog了。

#####一、创建一个简单的dialog

上一篇博客我们介绍了Activity的Window创建过程及最后如何展示出来的,这一篇我们接着将Dialog的Window创建及展示。先来看一个简单的dialog的例子, 代码如下:

private void dialogTest() {
	Dialog dialog = new Dialog(this);
	//创建dialog的内容为一个LinearLayout, 内部包含了一个textview和Button
	LinearLayout ll = new LinearLayout(this);
	ll.setGravity(Gravity.CENTER);
	ll.setPadding(100, 0, 100, 0);
	TextView title = new TextView(this);
	title.setText("标题");
	Button bt_confirm = new Button(this);
	bt_confirm.setText("确定");
	bt_confirm.setOnClickListener(new OnClickListener() {
		
		@Override
		public void onClick(View v) {
			Log.e(TAG, "点击确定");
		}
	});
	
	ll.setOrientation(LinearLayout.VERTICAL);
	ll.addView(title);
	ll.addView(bt_confirm);
	
	//给dialog设置布局文件
	dialog.setContentView(ll);
	
	dialog.show();
}

这样, 我们就创建出了一个dialog, 运行效果如下:

下面, 我们就进入Dialog的身体, 去看看它的源码吧~

#####一、创建Window

从构造函数看起, 首先初始化一个Dialog:

Dialog(Context context, int themeResId, boolean createContextThemeWrapper) {
    ...
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    final Window w = PolicyManager.makeNewWindow(mContext);
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    w.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);

    mListenersHandler = new ListenersHandler(this);
}

这里的Window依然是通过PolicyManager.makeNewWindow()创建的。这个过程和上一篇博客Activity的Window创建过程类似。 版本较高的代码, 这里可能看到的代码是这样的:

final Window w = new PhoneWindow(mContext);
mWindow = w;

细节虽然不同, 但是整体流程都是相同的

#####二、初始化DecorView并将Dialog的布局视图添加到DecorView中

在上面的示例代码里, 我们通过这一行代码,给dialog填充了布局:

//给dialog设置布局文件
dialog.setContentView(ll);

进入源码看一些这个setContentView方法:

public void setContentView(View view) {
    mWindow.setContentView(view);
}

可以看到, 这里通过我们第一步创建的Window来添加布局的, 上一章已经讲过了Window的唯一实现类是PhoneWindow,所以我们可以到PhoneWindow里去看看setContentView这个方法的源码。 其实从这里开始, 就和我们上一章分析Activity的Window添加的过程相似了,我们还是简单说下把。接着看 PhoneWindow的etContentView方法:

@Override
public void setContentView(View view) {
    setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    ...
   if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...
    } else {
        mContentParent.addView(view, params);
    }
	...
   
}

setContentView中有一些判断, 我们忽略掉, 毕竟PhoneWindow是公用代码,别的View来了也走这里, 判断比较多。其实从上面的示例代码也很简单, 我们并没有设置那么多参数啊flag之类的,所以 我们只看重点代码, 这里我们最终会调用 mContentParent.addView(view, params)这个方法, 这里的mContentParent 其实就是我们的Activity的 布局文件的View对象,这在上一篇博客已经分析过了, 所以这个Dialog的view 最终添加到了我们的Activity布局上。

还是Window添加的老套路, 故事并没有结束。。。

#####三、将DecorView添加到Window中显示

上面分析完了Dialog中的Window的创建, 布局View的添加, 但是我们的Demo示例代码还有最后一行没有看:dialog.show(),这才是让Dialog真正显示出来的方法, 我们在实际开发中也时常遇到, 就算把Dialog的布局文件都填充好了, 没调用show方法, 一样白忙活, 我们进入这里去看看:

public void show() {
   ...
    mDecor = mWindow.getDecorView();	
	...
    WindowManager.LayoutParams l = mWindow.getAttributes();
    ...
    try {
        mWindowManager.addView(mDecor, l);
        mShowing = true;

        sendShowMessage();
    } finally {
    }
}

到这里我们发现,和Activity的Window添加过程一样,最终还是通过WindowManager去添加方法到WindowManagerService中, 这样, 我们的Window才真正的展示了出来。

到这里, Dialog的Window创建过程,到最终View的显示 就讲解完了。

#####四、Dialog的关闭

当Dialog被关闭时, 它会通过WindowManager来移除DecorView, 同样最终会通过WindowManagerService来进行移除工作, 毕竟WindowManagerService是所有Window的管理者。这个过程不再赘述。

#####创建Dialog必须使用Activity的Context???

我们在开发中还经常遇到一个问题,如果传递给Dialog的参数不是Activity, 而是用Appliciation的话, 就会报错:

09-21 07:23:36.023: ERROR/AndroidRuntime(1550): FATAL EXCEPTION: main
09-21 07:23:36.023: ERROR/AndroidRuntime(1550): android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
09-21 07:23:36.023: ERROR/AndroidRuntime(1550): at android.view.ViewRoot.setView(ViewRoot.java:509)

从上面的错误信息可以看到,token null is not for an application, 这是因为Application没有应用token, 而应用token一般只有Activity才有。但是, 系统Window比较特殊, 它可以不需要token, 因此,在这个例子中,只需要将对话框指定为系统级Window即可, window的层级上一篇我们已经讲过了, 只需要制定WindowManager.LayoutParams中的type即可, 除此之外, 还需要在MAnifest中声明权限:

dialog.getWindow.setType(LayoutParams.TYPE_SYSTEM_ERROR);

权限声明:


这样, 我们的dialog就可以使用Application了~

喜欢的朋友, 麻烦点个赞吧~~

参考资料:

  • 《Android开发艺术探索》

你可能感兴趣的:(android源码分析,android应用)