Dialog、Toast的Window和ViewRootImpl

来源:互联网 发布:erp软件二次开发 编辑:程序博客网 时间:2024/05/20 13:36

前言

文章Activity中的Window的setContentView、遇见LayoutInflater&Factory、ViewRootImpl的独白,我不是一个View(布局篇) 分别讲述了Activity的setContentView添加ViewLayoutInflater布局解析以及添加Window。文章内容都是站在Activity的角度来进行代码解析的,因此我们不再对Dialog和Toast与Activity做具体分析,主要来看看它们与Activity有什么不同之处源码:android-22

Dialog

Dialog的构造

public class Dialog implements DialogInterface, Window.Callback,        KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{    //只有Activity的Context可以启动Dialog,因为Dialog展示的时候需要主题资源也就是ContextThemeWrapper。    Dialog(Context context, int theme, boolean createContextThemeWrapper) {        if (createContextThemeWrapper) {            if (theme == 0) {                TypedValue outValue = new TypedValue();                context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,                        outValue, true);                theme = outValue.resourceId;            }            mContext = new ContextThemeWrapper(context, theme);        } else {            mContext = context;        }        //因为每个上下文环境获取的系统服务都是相同的实例,这里获取的WindowManager是Activity的WindowManager。        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);        //创建Dialog的PhoneWindow对象。        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添加View

和Activity相同通过setContentView初始化Window中的DecorView,并对页面View进行add。详细讲述请移动到Activity中的Window的setContentView

public class Dialog implements DialogInterface, Window.Callback,        KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{    /**     * Set the screen content from a layout resource.  The resource will be     * inflated, adding all top-level views to the screen.     *      * @param layoutResID Resource ID to be inflated.     */    public void setContentView(int layoutResID) {        mWindow.setContentView(layoutResID);    }}

Dialog的展现

Dialog的展现和Activity不同是因为两者的声明周期不同,Activity的声明周期是有AMS调用而Dialog是应用程序自己调用的。ViewRootImpl的初始化在Activity会在onResume()方法之后,而是Dialog被调用show方法时触发的。

public class Dialog implements DialogInterface, Window.Callback,        KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{    /**     * Start the dialog and display it on screen.  The window is placed in the     * application layer and opaque.  Note that you should not override this     * method to do initialization when the dialog is shown, instead implement     * that in {@link #onStart}.     */    public void show() {        if (mShowing) {            if (mDecor != null) {                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);                }                mDecor.setVisibility(View.VISIBLE);            }            return;        }        mCanceled = false;        //判断是否调用onCreate方法        if (!mCreated) {            dispatchOnCreate(null);        }        //调用onStart方法        onStart();        //获取DecorView对象实例        mDecor = mWindow.getDecorView();        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {            final ApplicationInfo info = mContext.getApplicationInfo();            mWindow.setDefaultIcon(info.icon);            mWindow.setDefaultLogo(info.logo);            mActionBar = new WindowDecorActionBar(this);        }        //更新Window属性参数        WindowManager.LayoutParams l = mWindow.getAttributes();        if ((l.softInputMode                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();            nl.copyFrom(l);            nl.softInputMode |=                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;            l = nl;        }        try {            //Windowmanger添加Window、ViewRootImpl初始化并绑定Window            mWindowManager.addView(mDecor, l);            mShowing = true;            //OnShowListener监听回调            sendShowMessage();        } finally {        }    }}

Toast

Toast的构造

public class Toast {    final Context mContext;    final TN mTN;//    int mDuration;//展示时间    View mNextView;//所展示的View    /**     * Construct an empty Toast object.  You must call {@link #setView} before you     * can call {@link #show}.     *     * @param context  The context to use.  Usually your {@link android.app.Application}     *                 or {@link android.app.Activity} object.     */    //Context可以为Application也可以为Activity,    public Toast(Context context) {        mContext = context;        mTN = new TN();        mTN.mY = context.getResources().getDimensionPixelSize(                com.android.internal.R.dimen.toast_y_offset);        mTN.mGravity = context.getResources().getInteger(                com.android.internal.R.integer.config_toastDefaultGravity);    }    //NotificationManagerService的客户端IBinder对    private static INotificationManager sService;    private static class TN extends ITransientNotification.Stub {        TN() {            // XXX This should be changed to use a Dialog, with a Theme.Toast            // defined that sets up the layout params appropriately.            final WindowManager.LayoutParams params = mParams;            params.height = WindowManager.LayoutParams.WRAP_CONTENT;            params.width = WindowManager.LayoutParams.WRAP_CONTENT;            params.format = PixelFormat.TRANSLUCENT;            params.windowAnimations = com.android.internal.R.style.Animation_Toast;            //设置Window类型为Toast            params.type = WindowManager.LayoutParams.TYPE_TOAST;            params.setTitle("Toast");            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;        }    }   }

transient_notification.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:background="?android:attr/toastFrameBackground">    <TextView        android:id="@android:id/message"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_weight="1"        android:layout_gravity="center_horizontal"        android:textAppearance="@style/TextAppearance.Toast"        android:textColor="@color/bright_foreground_dark"        android:shadowColor="#BB000000"        android:shadowRadius="2.75"        /></LinearLayout>

Toast添加View

从Toast的调用我们开始分析Toast.makeText(MainActivity.this , "Hello World" , Toast.LENGTH_SHORT);我们主要看makeText方法。

public class Toast {    /**     * Make a standard toast that just contains a text view.     *     * @param context  The context to use.  Usually your {@link android.app.Application}     *                 or {@link android.app.Activity} object.     * @param text     The text to show.  Can be formatted text.     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or     *                 {@link #LENGTH_LONG}     *     */    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {        Toast result = new Toast(context);        //获取布局解析器        LayoutInflater inflate = (LayoutInflater)                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        //解析transient_notification.xml生成对应的View        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);        //找到View中的id为message的TextView        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);        //对Textview进行文字赋值        tv.setText(text);        //展示的Toast所用的View        result.mNextView = v;        //设置间隔时间        result.mDuration = duration;        return result;    }}

主要是对Toast内部成员变量mNextViewmDuration进行初始化。

Toast的展示

TOAST.png

将Toast内部的TN(ITransientNotification客户端对象)加入到INotificationManager服务端的Binder兑现的mToastQueue队列中。再由服务端循环遍历mToastQueue队列中ToastRecord对象,处理一个移除一个,每次处理的都是List的第一个ToastRecord对象。

public class Toast {    //INotificationManager的客户端的Binder对象    private static INotificationManager sService;    static private INotificationManager getService() {        if (sService != null) {            return sService;        }        //获取INotificationManager的客户端的Binder对象        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));        return sService;    }    /**     * Show the view for the specified duration.     */    public void show() {        //mNextView不能为空        if (mNextView == null) {            throw new RuntimeException("setView must have been called");        }        //service初始哈        INotificationManager service = getService();        //获取当前Context对应的包名        String pkg = mContext.getOpPackageName();        TN tn = mTN;        tn.mNextView = mNextView;        try {            //将TN加入INotificationManager中的mToastQueue队列            service.enqueueToast(pkg, tn, mDuration);        } catch (RemoteException e) {            // Empty        }    }}

NotificationManagerService在服务端处理ITransientNotification客户端传过来的enqueueToast事件。

public class NotificationManagerService extends SystemService {    //是否是系统调用    private static boolean isCallerSystem() {        return isUidSystem(Binder.getCallingUid());    }    private final IBinder mService = new INotificationManager.Stub() {        @Override        public void enqueueToast(String pkg, ITransientNotification callback, int duration){            if (DBG) {                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback                        + " duration=" + duration);            }            if (pkg == null || callback == null) {                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);                return ;            }            //判断是否是系统调动或者是Android系统应用程序进行调用            final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));            //Toast或者通知权限被禁用            if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {                if (!isSystemToast) {                    Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");                    return;                }            }            //mToastQueue加锁            synchronized (mToastQueue) {                int callingPid = Binder.getCallingPid();                long callingId = Binder.clearCallingIdentity();                try {                    ToastRecord record;                    //寻找当前callback在mToastQueue中的索引,没找到则返回-1                    int index = indexOfToastLocked(pkg, callback);                    // If it's already in the queue, we update it in place, we don't                    // move it to the end of the queue.                    //index>=0表示mToastQueue中有该callback的索引,record进行更新展示时间                    if (index >= 0) {                        record = mToastQueue.get(index);                        record.update(duration);                    } else {                        // Limit the number of toasts that any given package except the android                        // package can enqueue.  Prevents DOS attacks and deals with leaks.                        //不是系统的Toast                        if (!isSystemToast) {                            int count = 0;                            final int N = mToastQueue.size();                            for (int i=0; i<N; i++) {                                 final ToastRecord r = mToastQueue.get(i);                                 //判断当前的Toast是不是同一个包发出的                                 if (r.pkg.equals(pkg)) {                                     count++;                                     //当前包的需要展示的Toast缓存数量>=50                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {                                         Slog.e(TAG, "Package has already posted " + count                                                + " toasts. Not showing more. Package=" + pkg);                                         return;                                     }                                 }                            }                        }                        //根据callback等信息构造ToastRecord对象                        record = new ToastRecord(callingPid, pkg, callback, duration);                        //将新的ToastRecord对象加入到队列总                        mToastQueue.add(record);                        //加入之后当前的索引是lenth-1                        index = mToastQueue.size() - 1;                        //将当前包对应的线程切换为前台线程                        keepProcessAliveLocked(callingPid);                    }                    // If it's at index 0, it's the current toast.  It doesn't matter if it's                    // new or just been updated.  Call back and tell it to show itself.                    // If the callback fails, this will remove it from the list, so don't                    // assume that it's valid after this.                    //如果之前队列中没有正在处理的消息,那么处理当前这个ToastRecord                    if (index == 0) {                        showNextToastLocked();                    }                } finally {                    Binder.restoreCallingIdentity(callingId);                }            }        }    }}

NotificationManagerService使用先进先出(FIFO)的方式处理mToastQueue队列中的消息。
- 服务端的处理

public class NotificationManagerService extends SystemService {    void showNextToastLocked() {        //获取队列第一个ToastRecord        ToastRecord record = mToastQueue.get(0);        while (record != null) {            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);            try {                //调用客户端Binder对应的TN.show方法。                record.callback.show();                scheduleTimeoutLocked(record);                return;            } catch (RemoteException e) {                Slog.w(TAG, "Object died trying to show notification " + record.callback                        + " in package " + record.pkg);                // remove it from the list and let the process die                //当前Toast客户端Binder方法调用抛出异常                //移除当前ToastRecord                int index = mToastQueue.indexOf(record);                if (index >= 0) {                    mToastQueue.remove(index);                }                //切换当前ToastRecord进程                keepProcessAliveLocked(record.pid);                //遍历对象变为列表下一个oastRecord对象                if (mToastQueue.size() > 0) {                    record = mToastQueue.get(0);                } else {                    record = null;                }            }        }    }}
  • 客户端的处理
private static class TN extends ITransientNotification.Stub {    /**     * schedule handleShow into the right thread     */    @Override    public void show() {        if (localLOGV) Log.v(TAG, "SHOW: " + this);        mHandler.post(mShow);    }    final Runnable mShow = new Runnable() {        @Override        public void run() {            handleShow();        }    };    //展示Toast    public void handleShow() {        if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView                + " mNextView=" + mNextView);        //判断mNextView是否展示过        if (mView != mNextView) {            // remove the old view if necessary            //移除当前展示的Toast            handleHide();            mView = mNextView;            //获取当前的应用程序的上下文环境            Context context = mView.getContext().getApplicationContext();            //获取当前包名            String packageName = mView.getContext().getOpPackageName();            if (context == null) {                context = mView.getContext();            }            //获取上下文环境的WindowManagerImpl            mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);            // We can resolve the Gravity here by using the Locale for getting            // the layout direction            final Configuration config = mView.getContext().getResources().getConfiguration();            final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());            //设置参数的重力防线            mParams.gravity = gravity;            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {                mParams.horizontalWeight = 1.0f;            }            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {                mParams.verticalWeight = 1.0f;            }            //设置参数的坐标和偏移量            mParams.x = mX;            mParams.y = mY;            mParams.verticalMargin = mVerticalMargin;            mParams.horizontalMargin = mHorizontalMargin;            mParams.packageName = packageName;            //如果mView添加过,那么先把mView从WindowManager中移除。            if (mView.getParent() != null) {                if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);                mWM.removeView(mView);            }            if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);            //把需要展示的View添加在WindowManager中            mWM.addView(mView, mParams);            trySendAccessibilityEvent();        }    }}

Toast的消失

系统的Toast的hide都是在INotificationManager的服务端Binder中发起的,但最终的执行都是在INotificationManager的客户端Binder中执行的。

  • 服务端
public class NotificationManagerService extends SystemService {    private final class WorkerHandler extends Handler{        @Override        public void handleMessage(Message msg){            switch (msg.what){                case MESSAGE_TIMEOUT:                    //调用当前的Toast的hide                    handleTimeout((ToastRecord)msg.obj);                    break;                case MESSAGE_SAVE_POLICY_FILE:                    handleSavePolicyFile();                    break;                case MESSAGE_SEND_RANKING_UPDATE:                    handleSendRankingUpdate();                    break;                case MESSAGE_LISTENER_HINTS_CHANGED:                    handleListenerHintsChanged(msg.arg1);                    break;                case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED:                    handleListenerInterruptionFilterChanged(msg.arg1);                    break;            }        }    }    //让当前Toast展示一段时间后消失    private void scheduleTimeoutLocked(ToastRecord r){        //移除mHandler关于这个TaostRecord的所有Message        mHandler.removeCallbacksAndMessages(r);        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;        //发送一个delayed=duration的MESSAGE_TIMEOUT事件        mHandler.sendMessageDelayed(m, delay);    }    //使Toast消失    private void handleTimeout(ToastRecord record){        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);        synchronized (mToastQueue) {            //找当前ToastRecord在mToastQueue队列中的索引            int index = indexOfToastLocked(record.pkg, record.callback);            if (index >= 0) {                cancelToastLocked(index);            }        }    }    //调用当前索引=index的ToastRecord.callback.hide    void cancelToastLocked(int index) {        ToastRecord record = mToastQueue.get(index);        try {            ////调用客户端Binder对应的TN.hide方法。            record.callback.hide();        } catch (RemoteException e) {            Slog.w(TAG, "Object died trying to hide notification " + record.callback                    + " in package " + record.pkg);            // don't worry about this, we're about to remove it from            // the list anyway        }        //移除处理完的ToastRecord        mToastQueue.remove(index);        keepProcessAliveLocked(record.pid);        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.            //处理队列中的下一个ToastRecord            showNextToastLocked();        }    }}
  • 客户端
private static class TN extends ITransientNotification.Stub {    /**     * schedule handleHide into the right thread     */    @Override    public void hide() {        if (localLOGV) Log.v(TAG, "HIDE: " + this);        mHandler.post(mHide);    }    final Runnable mHide = new Runnable() {        @Override        public void run() {            handleHide();            // Don't do this in handleHide() because it is also invoked by handleShow()            mNextView = null;        }    };    public void handleHide() {        if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);        if (mView != null) {            // note: checking parent() just to make sure the view has            // been added...  i have seen cases where we get here when            // the view isn't yet added, so let's try not to crash.            if (mView.getParent() != null) {                if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);                //调用WindowManager的removeView移除mView                mWM.removeView(mView);            }            mView = null;        }    }}

总结

通过分析ActivityDialogToast通过对ViewRootImpl的更细节的分析,所有添加在窗口上的View都有一个ViewRootImpl作为它的Parent,处理View的布局、事件处理等。

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦~!~!

想阅读作者的更多文章,可以查看我 个人博客 和公共号:

振兴书城

原创粉丝点击