理解Window和WindowManager(二)

##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上我们也分析完毕啦。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪舞飞影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值