##1、背景##
通过对前一篇文章的分析我们已经知道了Window的内部机制,其实主要就是对View进行添加、删除和更新。如果大家对Window的内部机制不是很清楚的可以先去了解一下理解Window和WindowManager(一)。
大家都知道,View是Android的视图呈现,但是View却不能单独存在而需要依附在Window之上,因此有视图的地方肯定就有Window,因此Activity、Dialog和Toast等视图都对应着一个Window,本文将分析这些视图元素中的Window创建过程。
##2、视图元素中Window的创建过程##
###1、Activity的Window创建过程###
要分析Activity中Window的创建过程就必须要了解Activity的启动过程,Activity的启动过程很复杂,在这里我们就不详细阐述了,大家想了解的话可以看我的另外一篇文章Activity启动流程源码分析之入门(一),最终会调用ActivityThread.performLaunchActivity()方法,并在其中调用Activity的attach()方法,我们进入该方法。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,...) {
//通过调用PolicyManager的makeNewWindow创建Window对象
mWindow = PolicyManager.makeNewWindow(this);
//Activity实现了Window的Callback接口,并设置监听
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.Callback接口,因此当Window接受到外界状态变化时就会回调Activity的方法,Callback接口中的方法比较多,比如常见的dispatchTouchEvent、onContentChanged、onAttachedToWindow、onDetachedFromWindow等。
另外Window的创建是通过PolicyManager类,我们进入该类。
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
//通过类加载方式加载Policy类
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
//获得IPolicy对象
sPolicy = (IPolicy)policyClass.newInstance();
public static Window makeNewWindow(Context context) {
//调用IPolicy对象的makeNewWindow方法
return sPolicy.makeNewWindow(context);
}
}
从以上代码可以看出,PolicyManager类首先获得IPolicy对象,然后将操作转给IPolicy对象的方法,我们这里先看一下IPolicy类。
public interface IPolicy {
public Window makeNewWindow(Context context);
public LayoutInflater makeNewLayoutInflater(Context context);
public WindowManagerPolicy makeNewWindowManager();
public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
可以看出IPolicy是一个接口类,其真正的实现类是Policy,那就相当于PolicyManager将实际操作转交给Policy类进行处理,我们看一下Policy类。
public class Policy implements IPolicy {
private static final String[] preload_classes = {
"com.android.internal.policy.impl.PhoneLayoutInflater",
"com.android.internal.policy.impl.PhoneWindow",
"com.android.internal.policy.impl.PhoneWindow$1",
"com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
"com.android.internal.policy.impl.PhoneWindow$DecorView",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
};
//预加载类
for (String s : preload_classes) {
Class.forName(s);
}
//makeNewWindow类的真正实现
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
}
从Policy源码中可以看到makeNewWindow的真正实现是创建一个PhoneWindow对象,从这里也可以验证Window的具体实现的确是PhoneWindow。
好啦,到此Window已经创建完成了,接下来就是如何将Activity视图附属在Window上了,由于Activity的视图由setContentView提供,所有我们看一下其源码。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
从以上可以看出,Activity将具体操作转交给Window处理,而Window的具体实现是PhoneWindow,所以只需看PhoneWindow的setContentView方法即可,这部分内容在之前的博客中已做分析,这里就不详细阐述了,大家有兴趣的可以去了解一下SetContentView与LayoutInflater源码分析。
好啦,到此Activity的Window创建过程就分析完毕啦,接下来我们在分析Dialog的创建过程。
###2、Dialog的Window创建过程###
Dialog的创建过程和Activity很相似,在这里我们就简单描述其过程。
1、创建Window
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//通过PolicyManager.makeNewWindow方法创建Window对象
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);
}
Dialog的Window创建过程和Activity很相似,都是通过PolicyManager.makeNewWindow方法创建,这里就不详细阐述了。
2、将Dialog的视图添加到DecorView中
public void setContentView(int layoutResID) {
mWindow.setContentView(layoutResID);
}
这部分和Activity也是相似的,通过调用PhoneWindow对象的setContentView方法来指定布局。
3、将DecorView添加到Window中并显示
public void show() {
mWindowManager.addView(mDecor, l);
mShowing = true;
}
在Dialog的show方法中,通过WindowManager将DecorView添加到Window中,当Dialog被关闭时会通过mWindowManager.removeViewImmediate(mDecor);来移除DecorView。
这里有个问题需要注意下,在创建Dialog时,需要传入Activity的Context,如果传入Application的Context会报错。
###3、Toast的Window创建过程###
Toast也是基于Window来实现的,但是其于Dialog不同,Toast具有定时取消的功能,所以采用了Handler。但是在Toast的内部有两类IPC过程,第一类是Toast访问NotificationManagerService,而第二类是NotificationManagerService回调Toast中的TN接口。
无论是Toast的系统默认样式还是通过setView来自定义View,其对应的布局文件都是Toast中的内部成员变量mNextView,我们先来看一下Toast的makeText和setView方法。
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
//创建TN对象
mTN = new TN();
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
//将系统默认的View赋值给mNextView
result.mNextView = v;
}
public void setView(View view) {
//自定义显示View
mNextView = view;
}
在makeText方法中创建TN对象,并且给mNextView变量赋值,接下来我们看一下Toast.show方法。
public void show() {
//首先判断视图是否为null
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
//获得INotificationManager对象,其真正实现者是NotificationManagerService
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
//赋值
TN tn = mTN;
tn.mNextView = mNextView;
try {
//调用NMS的enqueueToast方法,并将tn对象作为参数传递过去
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
首先我们来看一下如何获得INotificationManager对象,通过以下代码。
private static INotificationManager sService;
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
//通过Stub类的asInterface方法获得INotificationManager对象,其实质就是NWS对象
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
我们紧接着进入NWS对象的enqueueToast方法,这里需要主要将TN作为参数传递过去了。
//第一个参数表示当前应用的包名,第二个参数就是我们刚刚传入的TN对象,它是一个Binder对象
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
//......
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count++;
//mToastQueue列表最多同时只能包含50个ToastRecord对象
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
return;
}
}
}
//enqueueToast首先将Toast请求封装成ToastRecord对象并添加到mToastQueue的列表中
record = new ToastRecord(callingPid, pkg, callback, duration);
mToastQueue.add(record);
//通过调用showNextToastLocked方法来显示Toast
if (index == 0) {
showNextToastLocked();
}
}
该方法主要将Toast请求封装成ToastRecord对象,然后添加到mToastQueue列表中等待显示,最后调用showNextToastLocked一个一个显示,我们进入showNextToastLocked方法中。
void showNextToastLocked() {
//获得当前需要显示的Toast
ToastRecord record = mToastQueue.get(0);
while (record != null) {
//此时这里的record.callback即是之前我们传入的TN对象,并调用其show()方法
record.callback.show();
//发送延时消息
scheduleTimeoutLocked(record);
}
}
首先我们分析一下record.callback.show()这个方法,这是TN的show方法,我们看其源码。
public void show() {
mHandler.post(mShow);
}
public void hide() {
mHandler.post(mHide);
}
都是发送一个Handler请求,由于这里使用了Handler,所以Toast无法在没有Looper的线程中使用,我们紧接着看mShow和mHide的Runnable接口。
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
mNextView = null;
}
};
将操作转给handleShow和handleHide方法。
public void handleShow() {
mView = mNextView;
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//通过WindowManager的addView方法来显示视图
mWM.addView(mView, mParams);
}
public void handleHide() {
if (mView.getParent() != null) {
//通过WindowManager的removeView来取消视图
mWM.removeView(mView);
}
mView = null;
}
到这里相信大家已经明白了Toast是如何通过WindowManager来添加和删除视图的吧,但是我们还没有完,我们接着往下分析,我们回到showNextToastLocked方法中,查看其发送延时消息的scheduleTimeoutLocked方法。
private void scheduleTimeoutLocked(ToastRecord r)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
//时间判断
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
//发送延迟消息
mHandler.sendMessageDelayed(m, delay);
}
发送延迟消息后并最终调用cancelToastLocked方法,我们进入其源码。
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
//调用TN对象的hide方法
record.callback.hide();
//在mToastQueue列表中删除已经显示过的ToastRecord对象
mToastQueue.remove(index);
if (mToastQueue.size() > 0) {
// Show the next one. If the callback fails, this will remove
// it from the list, so don't assume that the list hasn't changed
// after this point.
//在此调用showNextToastLocked方法判断是否有下一个要显示的ToastRecord对象
showNextToastLocked();
}
}
首先调用TN的hide方法来隐藏本Toast视图,然后将其在列表中删除,紧接着进入下一个Toast的显示。
好啦,到此Toast如何在Window上显示视图也分析完毕啦,到这里3种常用视图是如何显示在Window上我们也分析完毕啦。