IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android源码分析(六)Window添加 -> 正文阅读

[移动开发]Android源码分析(六)Window添加

1 系统添加窗口添加的过程

这里以我们常用的 Toast 来分析添加到 SystemWindow 中的过程

  //Toast  
	public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
       //1. 实例化 Toast 对象
    		Toast result = new Toast(context, looper);
				
        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    		//2. 内部解析 XML 为一个 View 的过程
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);
				//3. 记住这里 mNextView 它就是我们待会要添加的对象
        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }


    final TN mTN;
    int mDuration;
    View mNextView;

    public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        mTN = new TN(context.getPackageName(), looper);
        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);
    }

通过 makeText 我们得知 Toast 主要就做了添加了一个默认布局的 View ,然后将 View 赋值给了 Toast 的 mNextView 变量,下面我们来看 Toast show 方法

//Toast.java
    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }
				//1. 拿到 NMS 代理类
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
      	//2. TN 接收 NMS 消息
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
          //3. 通知 NMS 调用 enqueueToast 方法
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

  private static class TN extends ITransientNotification.Stub {
    ...
  }

通过上面代码我们知道首先拿到 INotificationManager ,然后将创建好的 mTN 赋值给 TN 对象,它继承自 ITransientNotification.Stub Binder对象说明具备了接收进程间通信的功能,最后调用 INotificationManager 的 enqueueToast 方法来通知 NotificationManagerService,下面我们来看 NMS 的 enqueueToast 具体实现,代码如下:

//NotificationManagerService.java
public class NotificationManagerService extends SystemService {
  ....
      private final IBinder mService = new INotificationManager.Stub() {
        @Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
           ...

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    ...
                    //1. 
                    record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        
                    if (index == 0) {
                      //2. 
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }  
  }
  ....
  
}

    void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            try {
              	//3. 
                record.callback.show(record.token);
                scheduleTimeoutLocked(record);
                return;
            } catch (RemoteException e) {
               ....
        }
    }

通过上面代码我们知道在 Toast 调用到 NMS 中,然后构造 ToastRecord 对象将 pkg、callback 等对象传递进去,然后调用注释 3 record.callback.show 函数,这里的 callback 就是定义在 Toast 中接收 NMS 消息的,我们回到 Toast 中 show(Binder binder) 方法,代码如下:
?

//Toast.java
        @Override
        public void show(IBinder windowToken) {
            //1. 
            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
        }


            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            IBinder token = (IBinder) msg.obj;
                          //2. 
                            handleShow(token);
                            break;
                        }
                        ...
                        }
                    }
                }
            };

上面就是一个线程间通信操作,我们直接看2 具体实现

//Toast.java

        public void handleShow(IBinder windowToken) {
            ....
            if (mView != mNextView) {
  							...
                /**
                 * 1. 添加的对象
                 */
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                /**
                 * 1. 拿到 WindowManager
                 */
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                ...
                try {
                    /**
                     * 3. 调用 WindowManager addView 添加
                     */
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }

注释 3 ,调用 WindowManager 的 addView 方法,通过上面的介绍,我们知道 WindowManager 的实现是在 WindowManagerImpl ,具体实现代码如下:

//WindowManagerImpl.java

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        /**
         * 委托给 WindowManagerGlobal 来处理 addView
         */
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

addView 的第一个参数类型为 View ,说明窗口都是以 View 的形式存在的,addView 方法中会调用 WindowManagerGlobal 对象中 addView 方法,

//
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    ...

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            /**
             * 1. 根据 WindowManager.LayoutParams 的参数来对添加的子窗口进行相应的调整
             */
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            ...
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...
            /**
             * 2. 实例化 ViewRootImpl 对象,并赋值给 root 变量
             */
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            /**
             * 3. 添加 view 到 mViews 列表中
             */
            mViews.add(view);
            /**
             * 4. 将 root 存储在 ViewRootImp 列表中
             */
            mRoots.add(root);
            /**
             * 5. 将窗口的参数保存到布局参数列表中。
             */
            mParams.add(wparams);

            try {
                /**
                 * 6. 将窗口和窗口的参数通过 setView 方法设置到 ViewRootImpl 中
                 */
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

总结上述主要做了以下操作:

  • 根据 WindowManager.LayoutParams 的参数来对添加的子窗口进行相应的调整.
  • 实例化 ViewRootImpl 对象,并赋值给 root 变量
  • 添加 view 到 mViews 列表中
  • 将 root 存储在 ViewRootImp 列表中
  • 将窗口的参数保存到布局参数列表中
  • 将窗口和窗口的参数通过 setView 方法设置到 ViewRootImpl 中

ViewRootImpl 身负很多职责

  1. View 树的根并管理 View 树
  2. 触发 View 的测量、布局、绘制
  3. 输入事件的中转站
  4. 管理 surface
  5. 负责与 WMS 进行系统间通信
    //ViewRootImpl.java
     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
         ...
          try {
                        mOrigWindowType = mWindowAttributes.type;
                        mAttachInfo.mRecomputeGlobalAttributes = true;
                        collectViewAttributes();
                        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
                    } catch (RemoteException e) {
                       ...
                    }  
           
         ...
          
        }
       
     }
    

到了 ViewRootImpl 的职责后,我们接着来查看 ViewRootImpl 的 setView 方法:

//ViewRootImpl.java
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
     ...
      try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                   ...
                }  
       
     ...
      
    }
   
 }

setView 方法中有很多逻辑,这里只截取重要代码,主要就是调用 mWindowSession 类型的 WindowSession 的 addToDisplay 方法,它是一个 Binder 对象,它的服务端的实现是 Session 类。看下Session代码

//Session.java
public class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
  
  final WindowManagerService mService;
  ...
        @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
  ...
  
}
        
        

我们知道本地进程的 ViewRootImpl 想要和 WMS 进行通信需要经过 Session, 那么 Session 为何包含在 WMS 中呢? 我们看上面代码知道,Session 内部的 addToDisplay 方法,其实就是调用 WMS 的 addWindow 方法。并将自身也就是 Session 作为参数传递进去,那么这样每一个应用程序进程都会对应一个 Session ,WMS 会调用 ArrayList 来保存这些 Session。这也是为什么 WMS 包裹 Session 的原因了。这样剩下的工作就交给 WMS 来处理了,在 WMS 中会为这个添加的窗口分配 Surface ,可见负责显示界面的是 Surface 而不是 Window. WMS 会将它所管理的 Surface 交由 SurfaceFlinger 处理,SurfaceFlinger 会将这些 Surface 混合并绘制到屏幕上。

Activity 的添加过程

Activity 在启动过程中,如果 Activity 所在的进程不存在则会创建新的进程,创建新的进程之后就会运行代表主线程的实例 ActivityThread, ActivityThread 管理着当前应用程序进程的线程,这在 Activity 的启动过程中运用得很明显,当界面要与用户进行交互时,会调用 ActivityThread 的 handleResumeActivity 方法,如下所示:

//ActivityThread.java
    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
      ...

        /**
         * 1. 最终会调用 Activity onResume 生命周期函数
         */
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
          ...
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                /**
                 * 2. 得到 ViewManager 对象
                 */
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        /**
                         * 3. 调用 ViewManager 的 addView 方法
                         */
                        wm.addView(decor, l);
                    } else {
                      
                        a.onWindowAttributesChanged(l);
                    }
                }

           ...
        }
    }

其一,通过注释 1 会执行 Activity 的 onResume 生命周期函数,其二,拿到 ViewManager 对象,这里 ViewManager 在之前介绍过,它是一个接口由 WindowManager 继承,最后在 WindowManagerImpl 中具体实现,所以其三,注释三就是调用 WindowManagerImpl 的 addView 方法, 剩下的调用跟 Toast 的执行流程一样

Window 的更新过程

Window 的更新过程和 Window 的添加过程是类似的,需要调用 ViewManager 的 updateViewlayout 方法,updateViewLayout 方法在 WindowmanagerImpl 中实现, WindowManagerImpl 的 updateViewLayout 方法的调用 WindowManagerGlobal 的 updateViewLayout 方法


?

//WindowManagerGlobal.java
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        /**
         * 1. 将更新的参数设置到 View 中
         */
        view.setLayoutParams(wparams);

        synchronized (mLock) {
            /**
             * 2. 得到要更新的窗口在 View 列表中的索引
             */
            int index = findViewLocked(view, true);
            /**
             * 3. 根据索引得到窗口的 ViewRootImpl
             */
            ViewRootImpl root = mRoots.get(index);
            /**
             * 4. 删除旧的参数
             */
            mParams.remove(index);
            /**
             * 5.重新添加新的参数
             */
            mParams.add(index, wparams);
            /**
             * 6.将更新的参数设置到 ViewRootImpl 中
             */
            root.setLayoutParams(wparams, false);
        }
    }

看下注释 6 的代码:

//ViewRootImpl.java
  void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
    ...
      
    scheduleTraversals();
    
  }
//ViewRootImpl.java
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {
  ....
}

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            /**
             * mChoreographer:用于接收显示系统的 VSync 信号,在下一帧渲染时控制执行一些操作,
             * 用于发起添加回调 在 mTraversalRunnable 的 run 中具体实现
             */
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

根据注释我们直接看 TraversalRunnable 的 run 实现,代码如下:

//ViewRootImpl.java
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
          //调用内部的 doTraversal 方法
            doTraversal();
        }
    }
//ViewRootImpl.java

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

在 doTraversal 中又调用了 performTraversals 方法

//ViewRootImpl.java
private void performTraversals() {
  /**
   * 1. 内部会调用 IWindowSession 的 relayout 方法来更新 Window 视图
   */
   relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
  ...
  /**
   * 2.内部调用 View 的 measure 测试方法
   */
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
   /**
   * 3. 内部会调用 View 的 layout 完成 View 的布局工作
   */
   performLayout(lp, mWidth, mHeight);
  ...
  /**
   * 4. 内部会调用 View 的 draw 方法,完成了 View 的绘制工作。
   */
   performDraw();
  ...
}

通过上面 performTraversals 函数,内部进行了 View 的 measure、layout、draw 这样就完成了 View 的绘制流程。在 performTraversals 方法中更新了 Window 视图,这样就完成了 Window 的更新。


?

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-18 17:30:27  更:2021-10-18 17:30:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 21:58:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码