精华内容
下载资源
问答
  • MUI窗口管理

    千次阅读 2016-07-12 18:50:32
    mui.back()仅处理窗口逻辑,若希望在窗口关闭之前再处理一些其它业务逻辑,则可将业务逻辑抽象成一个具体函数,然后注册为mui.init方法的beforeback参数;beforeback的执行逻辑为:执行beforeback参数对应的函数若...

    1、页面初始化

      在app开发中,若要使用HTML5+扩展api,必须等plusready事件发生后才能正常使用,mui将该事件封装成了mui.plusReady()方法,涉及到HTML5+的api,建议都写在mui.plusReady方法中。如下为打印当前页面URL的示例:

    mui.plusReady(function(){
        console.log("当前页面URL:"+plus.webview.currentWebview().getURL());
    });
    mui.init()    mui插件初始化
    mui.ready()    当DOM准备就绪时,指定一个函数来执行。

    代码块激活字符: minit


    2、创建子页面

       在mobile app开发过程中,经常遇到卡头卡尾的页面,此时若使用局部滚动,在android手机上会出现滚动不流畅的问题; mui的解决思路是:将需要滚动的区域通过单独的webview实现,完全使用原生滚动。具体做法则是:将目标页面分解为主页面和内容页面,主页面显示卡头卡尾区域,比如顶部导航、底部选项卡等;内容页面显示具体需要滚动的内容,然后在主页面中调用mui.init方法初始化内容页面。

    mui.init({
        subpages:[{
          url:your-subpage-url,//子页面HTML地址,支持本地地址和网络地址
          id:your-subpage-id,//子页面标志
          styles:{
            top:subpage-top-position,//子页面顶部位置
            bottom:subpage-bottom-position,//子页面底部位置
            width:subpage-width,//子页面宽度,默认为100%
            height:subpage-height,//子页面高度,默认为100%
            ......
          },
          extras:{}//额外扩展参数
        }]
      });

      参数说明:styles表示窗口属性,参考5+规范中的WebviewStyle;特别注意,height和width两个属性,即使不设置,也默认按100%计算;因此若设置了top值为非"0px"的情况,建议同时设置bottom值,否则5+ runtime根据高度100%计算,可能会造成页面真实底部位置超出屏幕范围的情况;left、right同理。

    示例:Hello mui的首页其实就是index.html加list.html合并而成的,如下:


      index.html的作用就是显示固定导航,list.html显示具体列表内容,列表项的滚动是在list.html所在webview中使用原生滚动,既保证了滚动条不会穿透顶部导航,符合app的体验,也保证了列表流畅滚动,解决了区域滚动卡顿的问题。 list.html就是index.html的子页面,创建代码比较简单,如下:

    mui.init({
        subpages:[{
          url:'list.html',
          id:'list.html',
          styles:{
            top:'45px',//mui标题栏默认高度为45px;
            bottom:'0px'//默认为0px,可不定义;
          }
        }]
      });


    代码块激活字符: misubpage


    3、打开新页面

      做web app,一个无法避开的问题就是转场动画;web是基于链接构建的,从一个页面点击链接跳转到另一个页面,如果通过有刷新的打开方式,用户要面对一个空白的页面等待;如果通过无刷新的方式,用Javascript移入DOM节点(常见的SPA解决方案),会碰到很高的性能挑战:DOM节点繁多,页面太大,转场动画不流畅甚至导致浏览器崩溃; mui的解决思路是:单webview只承载单个页面的dom,减少dom层级及页面大小;页面切换使用原生动画,将最耗性能的部分交给原生实现.

    mui.openWindow({
        url:new-page-url,
        id:new-page-id,
        styles:{
          top:newpage-top-position,//新页面顶部位置
          bottom:newage-bottom-position,//新页面底部位置
          width:newpage-width,//新页面宽度,默认为100%
          height:newpage-height,//新页面高度,默认为100%
          ......
        },
        extras:{
          .....//自定义扩展参数,可以用来处理页面间传值
        },
        createNew:false,//是否重复创建同样id的webview,默认为false:不重复创建,直接显示
        show:{
          autoShow:true,//页面loaded事件发生后自动显示,默认为true
          aniShow:animationType,//页面显示动画,默认为”slide-in-right“;
          duration:animationTime//页面动画持续时间,Android平台默认100毫秒,iOS平台默认200毫秒;
        },
        waiting:{
          autoShow:true,//自动显示等待框,默认为true
          title:'正在加载...',//等待对话框上显示的提示内容
          options:{
            width:waiting-dialog-widht,//等待框背景区域宽度,默认根据内容自动计算合适宽度
            height:waiting-dialog-height,//等待框背景区域高度,默认根据内容自动计算合适高度
            ......
          }
        }
    })


    参数:

    (1)、styles

       窗口参数,参考5+规范中的WebviewStyle;特别注意,height和width两个属性,即使不设置,也默认按100%计算;因此若设置了top值为非"0px"的情况,建议同时设置bottom值,否则5+ runtime根据高度100%计算,可能会造成页面真实底部位置超出屏幕范围的情况;left、right同理。

    (2)、extras

       新窗口的额外扩展参数,可用来处理页面间传值;例如:

    var webview = mui.openWindow({
    url:'info.html',
    extras:{
    name:'mui'  //扩展参数
    }
    });
    console.log(webview.name);//输出mui字符串

    注意:扩展参数仅在打开新窗口时有效,若目标窗口为预加载页面,则通过mui.openWindow方法打开时传递的extras参数无效。

    (3)、createNew

       是否重复创建相同id的webview;为优化性能、避免app中重复创建webview,mui v1.7.0开始增加createNew参数,默认为false;判断逻辑如下:

        a、createNew参数为为true,则不判断重复,每次都新建webview;

        b、createNew参数为为fasle,则先查找当前App中是否已存在同样id的webview,若存在则直接显示;否则新创建并根据show参数执行显示逻辑;

       注意:plusReady事件仅在webview首次创建时触发,使用mui.openWindow方法多次打开已存在的同样id的webview时,是不会重复触发plusReady事件的; 因此若业务写在plusReady事件中,可能会出现执行结果和预期不一致的情况;此时可通过自定义事件触发。

    (4)、show

      窗口显示控制参数,具体参数如下:

        a、autoShow:目标窗口loaded事件发生后,是否自动显示,默认为true;若为false,则仅创建但不显示webview;若目标页面为预加载页面,则该参数无效;

       b、aniShow表示页面显示动画,比如从右侧划入、从下侧划入等,具体可参考5+规范中的AnimationTypeShow

       c、duration:显示Webview窗口动画的持续时间,单位为ms

    (5)、waiting

      系统等待框参数。mui框架在打开新页面时等待框的处理逻辑为:显示等待框-->创建目标页面webview-->目标页面loaded事件发生-->关闭等待框;因此,只有当新页面为新创建页面(webview)时,会显示等待框,否则若为预加载好的页面,则直接显示目标页面,不会显示等待框。waiting中的具体参数:

       a、autoShow:是否自动显示等待框,默认为true;若为false,则不显示等待框;注意:若waiting框的autoShow为true,但目标页面不自动显示,则需在目标页面中通过如下代码关闭等待框:plus.nativeUI.closeWaiting();

       b、title:等待框上的提示文字

       c、options表示等待框显示参数,比如宽高、背景色、提示文字颜色等,具体可参考5+规范中的WaitingOption

    示例1:Hello mui中,点击首页右上角的图标,会打开关于页面,实现代码如下:

    //tap为mui封装的单击事件,可参考手势事件章节
    document.getElementById('info').addEventListener('tap', function() {
      //打开关于页面
      mui.openWindow({
        url: 'examples/info.html',
        id:'info'
      });
    });

    因没有传入styles参数,故默认全屏显示;也没有传入show参数,故使用slide-in-right动画,新页面从右侧滑入。

    示例2:A页面打开B页面,B页面为一个需要从服务端加载的列表页面,若在B页面loaded事件发生时就将其显示出来,因服务器数据尚未加载完毕,列表页面为空,用户体验不好;可通过如下方式改善用户体验(最好的用户体验应该是通过预加载的方式):

    第一步,B页面loaded事件发生后,不自动显示;

    //A页面中打开B页面,设置show的autoShow为false,则B页面在其loaded事件发生后,不会自动显示;
    mui.openWindow({
        url: 'B.html',
        show:{
          autoShow:false
        }
      });

    第二步,在B页面获取列表数据后,再关闭等待框、显示B页面

    //B页面onload从服务器获取列表数据;
    window.onload = function(){
      //从服务器获取数据
      ....
      //业务数据获取完毕,并已插入当前页面DOM;
      //注意:若为ajax请求,则需将如下代码放在处理完ajax响应数据之后;
      mui.plusReady(function(){
        //关闭等待框
        plus.nativeUI.closeWaiting();
        //显示当前页面
        mui.currentWebview.show();
      });
    }

    代码块激活字符: mopenwindow

     

    4、关闭页面

       mui框架将窗口关闭功能封装在mui.back方法中,具体执行逻辑是:若当前webview为预加载页面,则hide当前webview;否则,close当前webview;

    在mui框架中,有三种操作会触发页面关闭(执行mui.back方法):

     a、点击包含.mui-action-back类的控件

     b、在屏幕内,向右快速滑动

     c、Android手机按下back按键

     iOS平台原生支持从屏幕边缘右滑关闭。iOS平台可通过popGesture参数实现从屏幕边缘右滑关闭webview,参考5+规范,若想禁用该功能,可通过setStyle方法设置popGesture为none。

      hbuilder中敲mheader生成的代码块,会自动生成带有返回导航箭头的标题栏,点击返回箭头可关闭当前页面,原因就是因为该返回箭头包含.mui-action-back类,代码如下:

    <header class="mui-bar mui-bar-nav">
    <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
    <h1 class="mui-title">标题</h1>
    </header>

      若希望在顶部导航栏之外的其它区域添加关闭页面的控件,只需要在对应控件上添加.mui-action-back类即可,如下为一个关闭按钮示例:

    <button type="button" class='mui-btn mui-btn-danger mui-action-back'>关闭</button>

      mui框架封装的页面右滑关闭功能,默认未启用,若要使用右滑关闭功能,需要在mui.init();方法中设置swipeBack参数,如下:

    mui.init({
    swipeBack:true //启用右滑关闭功能
    });

      mui框架默认会监听Android手机的back按键,然后执行页面关闭逻辑; 若不希望mui自动处理back按键,可通过如下方式关闭mui的back按键监听;

    mui.init({
    keyEventBind: {
    backbutton: false  //关闭back按键监听
    }
    });

      除了如上三种操作外,也可以直接调用mui.back()方法,执行窗口关闭逻辑;mui.back()仅处理窗口逻辑,若希望在窗口关闭之前再处理一些其它业务逻辑,则可将业务逻辑抽象成一个具体函数,然后注册为mui.init方法的beforeback参数;beforeback的执行逻辑为:执行beforeback参数对应的函数若返回false,则不再执行mui.back()方法;否则(返回true或无返回值),继续执行mui.back()方法;

    示例:从列表打开详情页面,从详情页面再返回后希望刷新列表界面,此时可注册beforeback参数,然后通过自定义事件通知列表页面刷新数据,示例代码如下:

    mui.init({
    beforeback: function(){
    //获得列表界面的webview
    var list = plus.webview.getWebviewById('list');
    //触发列表界面的自定义事件(refresh),从而进行数据刷新
    mui.fire(list,'refresh');
    //返回true,继续页面关闭逻辑
    return true;
    }
    });

    注意:beforeback的执行返回必须是同步的(阻塞模式),若使用nativeUI这种异步js(非阻塞模式),则可能会出现意想不到的结果;比如:通过plus.nativeUI.confirm()弹出确认框,可能用户尚未选择,页面已经返回了(beforeback同步执行完毕,无返回值,继续执行mui.back()方法,nativeUI不会阻塞js进程):在这种情况下,若要自定义业务逻辑,就需要复写mui.back方法了;如下为一个自定义示例,每次都需要用户确认后,才会关闭当前页面

    //备份mui.back,mui.back已将窗口关闭逻辑封装的比较完善(预加载及父子窗口),因此最好复用mui.back
    var old_back = mui.back;
    mui.back = function(){
      var btn = ["确定","取消"];
      mui.confirm('确认关闭当前窗口?','Hello MUI',btn,function(e){
        if(e.index==0){
        	//执行mui封装好的窗口关闭逻辑;
        	old_back();
        }
      });
    }

      为何设置了swipeBack: false,在iOS上依然可以右滑关闭?iOS平台原生支持从屏幕边缘右滑关闭,这个是通过popGesture参数控制的,参考5+规范,若需禁用,可通过setStyle方法设置popGesture为none。

      能否通过addEventListener增加back按键监听实现自定义关闭逻辑?addEventListener只会增加新的执行逻辑,老的监听逻辑(mui.back)依然会执行,因此,若需实现自定义关闭逻辑,一定要重写mui.back。

    代码块激活字符: mback

     

    5、预加载

       所谓的预加载技术就是在用户尚未触发页面跳转时,提前创建目标页面,这样当用户跳转时,就可以立即进行页面切换,节省创建新页面的时间,提升app使用体验。mui提供两种方式实现页面预加载。

    (1)、方式一:通过mui.init方法中的preloadPages参数进行配置.

    mui.init({
      preloadPages:[
        {
          url:prelaod-page-url,
          id:preload-page-id,
          styles:{},//窗口参数
          extras:{},//自定义扩展参数
          subpages:[{},{}]//预加载页面的子页面
        }
      ],
      preloadLimit:5//预加载窗口数量限制(一旦超出,先进先出)默认不限制
    });

      该种方案使用简单、可预加载多个页面,但不会返回预加载每个页面的引用,若要获得对应webview引用,还需要通过plus.webview.getWebviewById方式获得;另外,因为mui.init是异步执行,执行完mui.init方法后立即获得对应webview引用,可能会失败,例如如下代码:

    mui.init({
      preloadPages:[
        {
          url:'list.html',
          id:'list'
        }
      ]
    });
    var list = plus.webview.getWebviewByid('list');//这里可能返回空;

    (2)、方式二:通过mui.preload方法预加载.

    var page = mui.preload({
        url:new-page-url,
        id:new-page-id,//默认使用当前页面的url作为id
        styles:{},//窗口参数
        extras:{}//自定义扩展参数
    });

       通过mui.preload()方法预加载,可立即返回对应webview的引用,但一次仅能预加载一个页面;若需加载多个webview,则需多次调用mui.preload()方法;

    如上两种方案,各有优劣,需根据具体业务场景灵活选择;

    (3)、判断预加载是否成功

    方式一、通过直观现象分析

        预加载页面会立即打开,不会显示等待框;非预加载页面默认会先显示等待框,再显示新页面;

    方式二、增加log分析预加载页面是否已创建

        比如:A页面中预加载B页面,则在A页面完全加载(可通过setTimeout模拟)后,打印当前应用所有webview,看是否包含B页面的url,以此来分析。

    例如:在A页面增加如下代码:

    mui.plusReady(function(){
    setTimeout(function(){
    var array = plus.webview.all();
    if(array){
    for(var i=0,len=array.length;i<len;i++){
        	console.log(array[i].getURL());
            }
    }
    },5000)
    });

    代码块激活字符: minitpreload  mpreload(单个webview)

    展开全文
  • 系列文章解读&说明: Android Framework 窗口子系统 的 分析主要分为以下部分: Android Framework 窗口子系统 (01)WindowMangerService...Android Framework 窗口子系统 (03) 窗口显示次序 Android Fra...

    该系列文章总纲链接:专题分纲目录 Android Framework 窗口子系统


    本章关键点总结 & 说明:

    该图关注➕思维导图中右下方:窗口次序 部分即可。通过代码分析先说明了主序、次序、窗口类型的基础知识;之后说明了窗口次序的流程,排序和调整是如何实现的?同时解释了实现的原理。

    说明:手机屏幕是以左上角为原点,向右为X轴方向,向下为Y轴方向的一个二维空间。为方便管理窗口显示次序,手机屏幕被扩展为了一个三维空间,多定义了一个Z轴,方向为垂直于屏幕表面指向屏幕外。多个窗口依照其前后顺序排布在这个虚拟的Z轴上,因此窗口的显示次序又被称为Z序(Z order)。

    1 主序、子序、窗口显示类型

    从WS的构造方法开始分析,代码如下:

     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
               WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
               int viewVisibility, final DisplayContent displayContent) {
            ...
            //如果是子窗口,使用它依附的窗口类型来计算
            if ((mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW)) {
                mBaseLayer = mPolicy.windowTypeToLayerLw(
                        attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER
                        + WindowManagerService.TYPE_LAYER_OFFSET;//计算mBaseLayer,关键点1
                //表示子窗口和父窗口的相对位置
                mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);//计算mSubLayer,关键点2
                ...
            } else {//非子窗口,直接使用窗口类型来计算
                //注:TYPE_LAYER_MULTIPLIER的值是10000,TYPE_LAYER_OFFSET的值是1000
                mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
                        * WindowManagerService.TYPE_LAYER_MULTIPLIER
                        + WindowManagerService.TYPE_LAYER_OFFSET;
                mSubLayer = 0;//无效,仅在子窗口中有用
                ...
            }
            ...
        }

    由上可知,窗口显示次序由两个成员字段描述:主序mBaseLayer和子序mSubLayer。

    1. 主序用于描述窗口及其子窗口在所有窗口中的显示位置。主序越大,则窗口及其子窗口的显示位置相对于其他窗口的位置越靠前。
    2. 子序描述一个子窗口在其兄弟窗口中的显示位置。子序越大,相对于其兄弟窗口的位置越靠前。

    @1 关键点1的分析,这里继续分析mPolicy.windowTypeToLayerLw的内部实现,代码如下:

     //根据类型返回窗口的种类,从1-31,
        public int windowTypeToLayerLw(int type) {
            if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                return 2;
            }
            switch (type) {
            case TYPE_UNIVERSE_BACKGROUND:
                return 1;
            case TYPE_PRIVATE_PRESENTATION:
                return 2;
            case TYPE_WALLPAPER:
                // wallpaper is at the bottom, though the window manager may move it.
                return 2;
            case TYPE_PHONE:
                return 3;
            case TYPE_SEARCH_BAR:
                return 4;
            ...
            case TYPE_HIDDEN_NAV_CONSUMER:
                return 30;
            /// M:JB migration
            case TYPE_TOP_MOST:
                return 31;
            }
            Log.e(TAG, "Unknown window type: " + type);
            return 2;
        }

     这里相当于把窗口根据类型分成不同的层,但系统中同类型的窗口很多,因此最后将其*10000,相当于为同类型窗口保留10000个位置。
    @2 关键点2的分析,这里继续分析mPolicy.subWindowTypeToLayerLw的内部实现,代码如下:

    public int subWindowTypeToLayerLw(int type) {
            switch (type) {
            case TYPE_APPLICATION_PANEL:
            case TYPE_APPLICATION_ATTACHED_DIALOG:
                return APPLICATION_PANEL_SUBLAYER;//等于1
            case TYPE_APPLICATION_MEDIA:
                return APPLICATION_MEDIA_SUBLAYER;//等于-2
            case TYPE_APPLICATION_MEDIA_OVERLAY:
                return APPLICATION_MEDIA_OVERLAY_SUBLAYER;//等于-1
            case TYPE_APPLICATION_SUB_PANEL:
                return APPLICATION_SUB_PANEL_SUBLAYER;//等于2
            }
            Log.e(TAG, "Unknown sub-window type: " + type);
            return 0;
        }

    如果返回值<0,将位于所依附窗口的下面;否则将覆盖所依附窗口。

    于父窗口而言,主序取决于类型,子序则保持为0。子窗口主序与父窗口一样,子序取决于类型。窗口类型的主序和子序分别如下,窗口主序如下所示:

    窗口子序如下所示:

    特殊说明:

    1 MEDIA和MEDIA_OVERLAY为何为负数?

    这表明它们的显示次序位于其父窗口的后面。两个类型子窗口由SurfaceView控件创建。SurfaceView被实例化后,会向WMS添加一个类型为MEDIA的子窗口,它的父窗口就是承载SurfaceView控件的窗口。这个子窗口的Surface将被用于视频回放、相机预览或游戏绘制。为了不让这个子窗口覆盖住所有的父窗口中承载的其他控件(如拍照按钮,播放器控制按钮等),它必须位于父窗口之后。

    2 WALLPAPER类型的窗口的主序为何和APPLICATION类型的窗口主序相同?

    WALLPAPER类型的窗口需要在所有的APPLICATION窗口之间切换。因为有的Activity指定了android:windowShowWallpaper为true,则表示窗口要求将用户当前壁纸作为其背景。对于WMS来说,最简单的办法就是将WALLPAPER窗口放置到紧邻拥有这个式样的窗口的下方。在这种需求下,为了保证主序决定窗口顺序的原则,WALLPAPER使用了与APPLICATION相同的主序。

    3 如果有两个相同类型的窗口,那么它们的主序与子序会完全相同?

    以上描述的主序和子序仅仅是排序的依据之一,WMS需要根据当前所有同类型窗口的数量为每个窗口计算最终的现实次序。

    2 主序、次序 确定窗口次序

    WMS的addWindow()函数分析如下:

    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) {
        int res = mPolicy.checkAddPermission(attrs, appOp);
        //...
        synchronized(mWindowMap) {
    		//...
            final DisplayContent displayContent = getDisplayContentLocked(displayId);
            if (displayContent == null) {
                Slog.w(TAG, "Attempted to add window to a display that does not exist: "
                        + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            //...
            WindowToken token = mTokenMap.get(attrs.token);
            //...
    		// 新的WindowState对象在其构造函数中根据窗口类型初始化了其主序mBaseLayer和mSubLayer
            win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
    		
    		 
            //...
            if (type == TYPE_INPUT_METHOD) {
                win.mGivenInsetsPending = true;
                mInputMethodWindow = win;
                addInputMethodWindowToListLocked(win);
                imMayMove = false;
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
                mInputMethodDialogs.add(win);
                addWindowToListInOrderLocked(win, true);
                moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));
                imMayMove = false;
            } else {
    			// 将新的WindowState按显示次序插入到当前DisplayContent的mWindows列表中
                addWindowToListInOrderLocked(win, true);
                if (type == TYPE_WALLPAPER) {
                    mLastWallpaperTimeoutTime = 0;
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if (mWallpaperTarget != null
                        && mWallpaperTarget.mLayer >= win.mBaseLayer) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                }
            }
            //...
            // 根据窗口的排序结果,为DisplayContent的所有窗口分配最终的显示次序
            assignLayersLocked(displayContent.getWindowList());
            //...
        }
        //...
        return res;
    }

    这里关注2个关键点:

    1. addWindowToListInOrderLocked():将新建的WindowState按照一定的顺序插入到当前DisplayContent的mWindows列表中。它严格地按照显示顺序存储了所有窗口的WindowState。
    2. assignLayersLocked():将根据mWindows的存储顺序对所有的WindowState的主序和子序进行调整。

    下面针对2个关键点进行分析,对于一组窗口,这里是需要先排序窗口,之后再调整窗口,因此,这里分两部分来分析

    2.1 addWindowToListInOrderLocked分析
    @1 排序窗口对应的方法是addWindowToListInOrderLocked,代码如下:

        //根据窗口类型分三种情况处理
        private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) {
            if (win.mAttachedWindow == null) {
                final WindowToken token = win.mToken;
                int tokenWindowsPos = 0;
                if (token.appWindowToken != null) {
                    tokenWindowsPos = addAppWindowToListLocked(win);//Activity的顶层窗口,关键点1
                } else {
                    addFreeWindowToListLocked(win);//系统窗口,关键单2
                }
                if (addToToken) {
                    token.windows.add(tokenWindowsPos, win);//将WS对象插入到WindowToken列表中
                }
            } else {
                addAttachedWindowToListLocked(win, addToToken);//添加子窗口,关键点3
            }
            if (win.mAppToken != null && addToToken) {
                win.mAppToken.allAppWindows.add(win);//将WS对象插入到AppWindowToken列表中
            }
        }

    @@1.1 关键点1,addAppWindowToListLocked的分析

    private int addAppWindowToListLocked(final WindowState win) {
            final IWindow client = win.mClient;
            final WindowToken token = win.mToken;
            final DisplayContent displayContent = win.getDisplayContent();
            if (displayContent == null) {
                return 0;
            }
            final WindowList windows = win.getWindowList();
            final int N = windows.size();
            //这里通过token获取对应的tokenWindowList
            WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
            int tokenWindowsPos = 0;
            int windowListPos = tokenWindowList.size();
            //判断系统中是否存在和待插入窗口具有相同token的窗口,如果有说明这不是Activity的第一个窗口
            if (!tokenWindowList.isEmpty()) {
                //判定类型,是TYPE_BASE_APPLICATION则放在和它具有相同token窗口的下面
                if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
                    WindowState lowestWindow = tokenWindowList.get(0);
                    placeWindowBefore(lowestWindow, win);
                    tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows);
                } else {//不是TYPE_BASE_APPLICATION类型
                    AppWindowToken atoken = win.mAppToken;
                    WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
                    //判断是否位于最前面,即正在启动
                    if (atoken != null && lastWindow == atoken.startingWindow) {
                        placeWindowBefore(lastWindow, win);//放在启动窗口的下面
                        tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows);
                    } else {//判断不在最前面
                        //寻找同一应用中位置最高的窗口
                        int newIdx = findIdxBasedOnAppTokens(win);
                        //插入在它上面,表示加入的窗口将覆盖在前面窗口之上
                        windows.add(newIdx + 1, win);
                        if (newIdx < 0) {
                            tokenWindowsPos = 0;
                        } else {
                            tokenWindowsPos = indexOfWinInWindowList(
                                    windows.get(newIdx), token.windows) + 1;
                        }
                        mWindowsChanged = true;
                    }
                }
                return tokenWindowsPos;
            }
            //如果系统中不存在和待插入窗口具有相同token的窗口则继续执行下面的逻辑
            //说明Activity刚启动,第1个窗口还没有创建完毕
            WindowState pos = null;
            final ArrayList<Task> tasks = displayContent.getTasks();
            int taskNdx;
            int tokenNdx = -1;
            //遍历系统中所有的task以及task中包含的AppWindowToken,找到窗口的位置
            for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
                for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
                    final AppWindowToken t = tokens.get(tokenNdx);
                    if (t == token) {
                        --tokenNdx;
                        if (tokenNdx < 0) {
                            --taskNdx;
                            if (taskNdx >= 0) {
                                tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1;
                            }
                        }
                        break;
                    }
                    tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
                    if (!t.sendingToBottom && tokenWindowList.size() > 0) {
                        pos = tokenWindowList.get(0);
                    }
                }
                if (tokenNdx >= 0) {
                    break;
                }
            }
            ...
            //在task中排在本窗口前面的窗口中,找出距离自己最近的且token列表不为NULL的窗口
            //插入到它的最后一个子窗口的后面
            //如果前面窗口的列表也都是NULL,则寻找排在本窗口后面的第一个包含窗口对象的AppWindowToken
            //把本窗口插入到它前面
            //如果前后窗口的AppWinodwToken都为NULL,则重新遍历整个窗口,根据mBaseLayer的值来确定窗口的位置
            ...
            return tokenWindowsPos;
        }

     @@1.2 关键点2,addFreeWindowToListLocked的分析:

    private void addFreeWindowToListLocked(final WindowState win) {
            final WindowList windows = win.getWindowList();
            final int myLayer = win.mBaseLayer;
            int i;
            for (i = windows.size() - 1; i >= 0; i--) {//遍历同一设备所有的windows
                if (windows.get(i).mBaseLayer <= myLayer) {//比较mBaseLayer的值
                    break;
                }
            }//退出循环表示找到合适位置
            i++;
            windows.add(i, win);//插入到合适的位置
            mWindowsChanged = true;
        }

     @@1.3 关键点3,addAttachedWindowToListLocked的分析:

    private void addAttachedWindowToListLocked(final WindowState win, boolean addToToken) {
            final WindowToken token = win.mToken;
            final DisplayContent displayContent = win.getDisplayContent();
            if (displayContent == null) {
                return;
            }
            final WindowState attached = win.mAttachedWindow;
            WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
            // Figure out this window's ordering relative to the window it is attached to.
            final int NA = tokenWindowList.size();
            final int sublayer = win.mSubLayer;
            int largestSublayer = Integer.MIN_VALUE;
            WindowState windowWithLargestSublayer = null;
            int i;
            for (i = 0; i < NA; i++) {
                WindowState w = tokenWindowList.get(i);
                final int wSublayer = w.mSubLayer;
                if (wSublayer >= largestSublayer) {
                    largestSublayer = wSublayer;
                    windowWithLargestSublayer = w;
                }
                if (sublayer < 0) {
                    // For negative sublayers, we go below all windows in the same sublayer.
                    if (wSublayer >= sublayer) {
                        if (addToToken) {
                            token.windows.add(i, win);
                        }
                        placeWindowBefore(wSublayer >= 0 ? attached : w, win);
                        break;
                    }
                } else {
                    // For positive sublayers, we go above all windows in the same sublayer.
                    if (wSublayer > sublayer) {
                        if (addToToken) {
                            token.windows.add(i, win);
                        }
                        placeWindowBefore(w, win);
                        break;
                    }
                }
            }
            if (i >= NA) {
                if (addToToken) {
                    token.windows.add(win);
                }
                if (sublayer < 0) {
                    placeWindowBefore(attached, win);
                } else {
                    placeWindowAfter(largestSublayer >= 0? windowWithLargestSublayer: attached,win);
                }
            }
        }

    上面是窗口次序的代码分析,经过分析后,我们这里抽象出原则

    @2 原则如下所示

    @@2.1 子窗口排序规则:相对父窗口,根据子序排列;父窗口子序为0,因此:

    1. 子序为负数,会放在父窗口后面
    2. 子序为正数,会放在父窗口前面
    3. 新窗口子序==现有窗口子序,正数子序新窗口在前;负数子序新窗口在后

    @@2.2 非应用窗口排序规则:

    1. 依照主序排序,主序高在前;
    2. 新窗口子序==现有窗口子序,新窗口在前

    @@2.3 应用窗口排序规则:同一应用显示位置必须相邻。

    1. 当前应用已有窗口在显示,新窗口在该应用其他窗口前面。
    2. 当前应用第一个窗口,参照其他应用窗口顺序:插入最后一个应用,最后一个窗口后面 or 后面第一个应用,最前面窗口前面
    3. 没有参照应用窗口,根据主序将新窗口插入列表中。

    @3 输入法和壁纸窗口特殊说明
    窗口管理系统是按照叠层方式来管理所有的窗口,窗口的Z轴位置计算和设置过程如下图所示:

    WMS主要用来管理各个窗口的位置,确保正确显示;在所有窗口中,输入法窗口和壁纸窗口比较特殊

    •  输入法窗口需要显示在所有窗口的前面
    •  壁纸窗口位于当前Activity界面的下面:
    1. 如果希望Activity的背景显示壁纸,需要把background设置成透明/半透明
    2. 在LayoutParams属性中加FLAG_SHOW_WALLPAPER标志
    3. SurfaceFlinger合成图像时将显示位于Activity窗口下的壁纸

    2.2 assignLayersLocked分析

    @1 assignLayersLocked会根据窗口的排序结果,为DisplayContent的所有窗口分配最终的显示次序,代码实现如下:

    private final void assignLayersLocked(WindowList windows) {
            int N = windows.size();
            int curBaseLayer = 0;
            int curLayer = 0;
            int i;
            boolean anyLayerChanged = false;
            for (i=0; i<N; i++) {
                final WindowState w = windows.get(i);
                final WindowStateAnimator winAnimator = w.mWinAnimator;
                boolean layerChanged = false;
                int oldLayer = w.mLayer;
                //如果mBaseLayer和前一个相同,或者是输入法窗口/壁纸窗口
                if (w.mBaseLayer == curBaseLayer || w.mIsImWindow|| (i > 0 && w.mIsWallpaper)) {
                    curLayer += WINDOW_LAYER_MULTIPLIER;//注:WINDOW_LAYER_MULTIPLIER ==5
                    w.mLayer = curLayer;
                } else {
                    //如果mBaseLayer和前一个不同,则进入下一级的计算
                    curBaseLayer = curLayer = w.mBaseLayer;
                    w.mLayer = curLayer;
                }
                if (w.mLayer != oldLayer) {//如果层级改变,对下面的变量赋值
                    layerChanged = true;
                    anyLayerChanged = true;
                }
                //调整mAnimLayer的值
                final AppWindowToken wtoken = w.mAppToken;
                oldLayer = winAnimator.mAnimLayer;
                if (w.mTargetAppToken != null) {
                    winAnimator.mAnimLayer =
                            w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment;
                } else if (wtoken != null) {
                    winAnimator.mAnimLayer =
                            w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
                } else {
                    winAnimator.mAnimLayer = w.mLayer;
                }
                if (w.mIsImWindow) {
                    winAnimator.mAnimLayer += mInputMethodAnimLayerAdjustment;
                } else if (w.mIsWallpaper) {
                    winAnimator.mAnimLayer += mWallpaperAnimLayerAdjustment;
                }
                if (winAnimator.mAnimLayer != oldLayer) {
                    layerChanged = true;
                    anyLayerChanged = true;
                }
                final TaskStack stack = w.getStack();
                if (layerChanged && stack != null && stack.isDimming(winAnimator)) {
                    scheduleAnimationLocked();//执行动画
                }
            }
            if (mAccessibilityController != null && anyLayerChanged
                    && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) {
                mAccessibilityController.onWindowLayersChangedLocked();
            }
        }

    @2 mLayer值的计算就是从assignLayersLocked方法开始分析,该方法的目的是根据窗口的位置重新调整mLayer的值。
    说明:在assignLayersLocked方法之前,需要通过方法对窗口进行添加和排序,即在调整前是已经排序好了的
    调整方法:从最底层开始,具有相同mBaseLayer值作为一组,每组窗口mLayer值都是从mBaseLayer值开始,依次+WINDOW_LAYER_MULTIPLIER(=5)

    这样做的目的:同层窗口中每隔一个窗口就留下4个空位,方便下次插入新的窗口。同时,对于输入法和壁纸,做特殊处理,会和插入它位置前面的窗口处于一个层级,不通过mBaseLayer的值来计算。

    @3 mAnimLayer

    1. 当没有动画时,mAnimLayer==mLayer;
    2. 当窗口附属为一个Activity时,则会根据AppTokenAnimator的需要适当地增加一个矫正值。在动画完结后,mAnimLayer会被重新赋值为WindowState.mLayer,使得窗口回到其应有的位置。

    矫正值说明:它来自AppTokenAnimator所使用的Animation。当Animation要求动画对象的ZOrder必须位于其他对象之上时(Animation.getZAdjustment()的返回值为Animation.ZORDER_TOP),这个矫正值(1000)很大,于是窗口在动画过程中会显示在其他同主序的窗口之上。相反,如果要求ZOrder必须位于其他对象之下时,矫正为(-1000)很小,于是窗口会显示在其他同主序的窗口之下。

    3 总结

    窗口根据自己的类型得出其主序及子序,然后addWindowToListInOrderLocked()根据主序、子序以及其所属的Activity的顺序,按照升序排列在DisplayContent的mWindows列表中。然后assignLayersLocked()为mWindows中的所有窗口分配最终的显示次序

     

    展开全文
  • 跟踪窗口转换

    2020-12-07 15:53:01
    WinScope 提供了用于在窗口转换期间和转换后记录和分析 WindowManager 状态和 SurfaceFlinger 状态的基础架构和工具。WinScope 将所有相关的系统服务状态记录在一个跟踪文件中,您可以使用该文件重现并逐步查看转换...

    https://source.android.google.cn/devices/graphics/tracing-win-transitions

    记录一下Android 跟踪窗口转换 使用方法,每次都翻墙太麻烦.

    WinScope 提供了用于在窗口转换期间和转换后记录和分析 WindowManager 状态和 SurfaceFlinger 状态的基础架构和工具。WinScope 将所有相关的系统服务状态记录在一个跟踪文件中,您可以使用该文件重现并逐步查看转换。

    记录跟踪情况

    在运行 userdebug 或 eng 版本的设备上通过快捷设置或 adb 记录跟踪情况。

    快捷设置

    要通过快捷设置记录跟踪情况,请执行以下操作:

    1. 启用开发者选项
    2. 依次转到开发者选项 > 快捷设置开发者图块
    3. 启用 WinScope 跟踪
    4. 打开快捷设置
    5. 点按 Winscope 跟踪以启用跟踪。
    6. 在设备上执行窗口转换。
    7. 窗口转换完成后,打开快捷设置,然后点按 Winscope 跟踪以停用跟踪。

    跟踪记录会被写入 /data/misc/wmtrace/wm_trace.pb 和 /data/misc/wmtrace/layers_trace.pb,同时还会包含在错误报告中。

    adb

    在通过 adb 记录跟踪情况时,请分别记录 WindowManager 和 SurfaceFlinger 的跟踪情况。

    WindowManager 跟踪

    要记录 WindowManager 的跟踪情况,请执行以下操作:

    1. 启用跟踪:
      adb shell cmd window tracing start
    2. 停用跟踪:
      adb shell cmd window tracing stop
    3. 获取跟踪文件:
      adb pull /data/misc/wmtrace/wm_trace.pb wm_trace.pb

    您可以选择性地为 WindowManager 跟踪更改各种设置的默认日志配置:

    • 设置日志频率(针对事务或帧):
      adb shell cmd window tracing [frame&hairsp;|&hairsp;transaction]
    • 配置日志条目的 Verbose 级别:
      adb shell cmd window tracing level [all&hairsp;|&hairsp;trim&hairsp;|&hairsp;critical]
    • 设置缓冲区空间上限(以 KB 为单位):
      adb shell cmd window tracing size size-value
    • 转储缓冲区状态、日志级别、剩余容量和元素数量:
      adb shell cmd window tracing status

    SurfaceFlinger 跟踪

    要记录 SurfaceFlinger 的跟踪情况,请执行以下操作:

    1. 启用跟踪:
      adb shell su root service call SurfaceFlinger 1025 i32 1
    2. 停用跟踪:
      adb shell su root service call SurfaceFlinger 1025 i32 0
    3. 获取跟踪文件:
      adb pull /data/misc/wmtrace/layers_trace.pb layers_trace.pb

    您可以选择性地为 SurfaceFlinger 跟踪更改各种设置的默认日志配置:

    • 设置缓冲区空间上限(以 KB 为单位):
      adb shell su root service call SurfaceFlinger 1029 i32 size-value
    • 配置日志条目的 Verbose 级别:
      adb shell su root service call SurfaceFlinger 1033 i32 flags

    生成状态转储文件

    WinScope 可以从错误报告中读取 WindowManager 状态和 SurfaceFlinger 状态的快照。错误报告会将状态信息以单独的 Proto 文件的形式存储在 proto 文件夹中。要使用 adb 生成状态转储文件,请运行以下命令。

    WindowManager

    adb exec-out dumpsys window --proto > window_dump.pb

    SurfaceFlinger

    adb exec-out dumpsys SurfaceFlinger --proto > sf_dump.pb

    分析跟踪记录

    要分析跟踪文件,请使用 WinScope Web 应用。您可以在源代码的基础上编译此应用,也可以从预编译目录中打开此应用。

    1. 从 Android 源代码库中下载预编译的软件工件:
      curl 'https://android.googlesource.com/platform/prebuilts/misc/+/master/common/winscope/winscope.html?format=TEXT' | base64 -d > winscope.html
    2. 在网络浏览器中打开下载的软件工件。
    3. 打开 WinScope 后,选择打开文件以加载跟踪文件。

    使用 WinScope

    在 WinScope 中打开跟踪文件后,您便可以通过多种方式对该文件进行分析。

    WinScope 屏幕截图图 1. 在 WinScope 中分析跟踪记录

    • 时间轴 - 您可以通过时间轴查看跟踪记录中的事件序列。您可以使用箭头键或点击各个条目以浏览时间轴。
    • 屏幕 - 您可以在屏幕上直观地查看每个可见窗口。点击屏幕上的某个窗口即可选择层次结构中相应的源窗口。
    • 层次结构 - 您可以通过层次结构查看系统已知的每个窗口。有些窗口不包含缓冲区,它们存在的目的在于为其子项设置政策。可见窗口均标有 V 图标。
    • 属性 - 您可以在属性中查看层次结构中所选条目的状态信息。
    展开全文
  • 【Unity3D基础知识】——Stats窗口

    千次阅读 2017-05-29 00:26:07
    Stats窗口 ##Stats窗口,全程叫做 Rendering Statistics Window , 即渲染数据统计窗口,它会实时统计数据。 FPS(Time per frame andFPS) frames per seconds 表示引擎处理和渲染一个游戏帧所花费的时间,该数字...

    Stats窗口 ##

    Stats窗口,全程叫做 Rendering Statistics Window , 即渲染数据统计窗口,它会实时统计数据。

    Stats

    FPS(Time per frame andFPS)
    frames per seconds 表示引擎处理和渲染一个游戏帧所花费的时间,该数字主要受到场景中渲染物体数量和 GPU 性能的影响,FPS 数值越高,游戏场景的动画显示会更加平滑和流畅。
    一般来说,超过 30FPS 的画面人眼不会感觉到卡,有视觉残留的特性,光在视网膜上停止用后人眼还会保持 1/24 秒左右的时间,因此游戏画面每秒帧数至少要保证在 30 以上。
    另外,Unity 中的 FPS 数值仅包括此游戏 Scene 里更新和渲染的帧,编辑器中编辑的 Scene 和其它监视窗口的进程不包括在内。

    CPU
    获取到当前占用 CPU 进行计算的时间绝对值,或时间点,如果 Unity 主进程处于挂断或休眠状态时,CPU time将会保持不变。

    Render thread
    GPU 渲染进程处理图像所花费的时间,具体数值由 GPU 性能来决定。

    Batches 即 Batched Draw Calls, 是 Unity 内置的 Draw Call Batching 技术。
    一般来说,引擎每对一个物体进行一次 DrawCall,就会产生一个 Batch,这个 Batch 里包含着该物体所有的网格和顶点数据,当渲染另一个相同的物体时,引擎会直接调用 Batch 里的信息,将相关顶点数据直接送到 GPU, 从而让渲染过程更加高效,即 Batching技术是将所有材质相近的物体进行合并渲染。

    对于含有多个不同 ShaderMaterial 的物体,渲染的过程比较耗时,因为会产生多个 Batches。每次对物体的材质或者贴图进行修改,都会影响 Batches 里数据集的构成。因此,如果场景中有大量材质不同的物体,会很明显的影响到 GPU 的渲染效率。有关 Batches 优化相关的方案:

    1、 虽然 Unity 引擎自带 Draw Call Batching 技术,我们也可以通过手动的方式合并材质接近的物体;
    2、尽量不要修改 Batches 里物体的 Scale, 因为这样会生成新的 Batch。
    3、为了提升 GPU 的渲染 效率,应当尽可能的在一个物体上使用较少的材质,减少 Batches 过多的开销。
    4、对于场景中不会运动的物体,考虑设置 Static 属性,Static 声明的物体会自动进行内部批处理优化。

    SetPass calls
    在 Unity4.x 和 3.x 原来的 Stats 面板的第一项是“Draw calls”,然而到了 Unity5.X版本, Stats 上没有了“Draw calls”,却多出来一项“SetPass calls”。

    比如说场景中有 100gameObject,它们拥有完全一样的 Material,那么这 100 个物体很可能会被 Unity 里的 Batching 机制结合成一个 Batch。所以用 “Batchs”来描述 Unity 的渲染性能是不太合适,它只能反映出场景中需要批处理物体的数量。
    那么可否用 “Draw calls”来描述呢?答案同样是不适合。每一个“Draw calls”是CPU发送个 GPU 的一个渲染请求,请求中包括渲染对象所有的顶点参数、三角面、索引值、图元个数等,这个请求并不会占用过多的小号,真正消耗渲染资源的是在 GPU得到请求指令后,把指令发送给对应物体的 Shader, 让Shader 读取指令并通知相应的渲染通道(Pass)进行渲染操作。
    假设场景中有 1gameObject ,希望能显示很酷炫的效果,它的 Material 上带有许多特定的 Shader。为了实现相应的效果,Shader 里或许会包含很多的 Pass,每当GPU 即将去运行一个 Pass 之前,就会产生一个 “SetPass call”,因此在描述渲染性能开销上,“SetPass calls”更加有说服力。


    Batches and DrawCall这个是绘制图像的重要指标,可以作为衡量场景绘制效率的首要参考。
    一个 Draw Call, 等与呼叫一次 DrawlndexedPrimitive(DX) or glDrawElements(OGL),等于一个 Batch。
    NVIDIA 在 GDC 上曾提出 25k batch/sec 的渲染量会使 1GHzCPU 达到 100% 的使用率,因此使用公式:
    25K∗n(GHZ)∗Percentage/Framerate=Batch/Frame

    可以推算出某些 CPU 可以抗多少 Batch。例如红米手机 CPU 为 1.5Hz, 假设分出 20% 资源供渲染,希望游戏跑到 30 帧 。那么能抗多少 DrawCall ? 25k * 1.5 * 0.2 / 30 = 250。 因此从这方面也能看出,如果 CPU 不能分出更多的资源供渲染计算,能抗的 D人awCall 就会变少。
    在 Stats 面板中看到的 Batches 是渲染的总 Batch 这个值等于同于 DrawCall。 但 Unity 中可以获取到末批次处理之前的 DrawCall。 因此需要注意不要混淆感念。


    Saved By Batching :
    这个值是由于 Batch 减少的 DrawCall, 可以间接的看到场景优化的效果。这个值有两部分组成: Static BatchingDynamic Batching
    这个由 Unity 内建自动合并虽然优点多多,但也不是没有缺陷。静态合并会引发内存和存储的额外开销,动态合并会增加 CPU 的负担。 这部分内容参考 Unity 官方文档
    总体上讲所以希望批次渲染的元素要有相同的材质。通常两个材质如果只有贴图不同,可以将贴图合并到一张大图中,这就是所谓的和图。另外在使用 ShadowCaster 时,只要材质相同,即使贴图不同也可以合并渲染。
    Dynamic Batches

    动态合并在满足以下条件时时自动完成的额:

    • 模型总顶点数小于 900。
    • 不包含镜像 transform 改变。 不改变 Scale。
    • 如果使用动态 lightmap 需要指定正确。
    • 不使用多 Pass 的 Shader。

    由于需要在合并时通过 CPU 计算转为世界坐标,这项技术只在 CPU 消耗比 DrawCall 消耗“便宜”时才值得。这个衡量标准会根据平台产生差异,例如苹果平台上 DrawCAll的消耗便宜,就不应该使用这项技术。这个功能可以在 Editor–> Project Setting –>Player 中进行设置打开与关闭。



    Static Batches
    场景中不能移动的物件可以使用静态合并,它不受顶点数的限制,可以大幅较少 DrawCall。 但为了将元素合并到一个大模型中,这项技术需要额外的内存。主要的内存消耗在于共享多边形会在内存中重复创建。因此有时候需要牺牲渲染效率来避免静态合并,来保证内存够小。例如在茂密的树林中使用这项技术会导致大量的内存消耗。

    Verts
    摄像机视野(field of view)内渲染的顶点数。
    Tis
    摄像机视野(field of view)内渲染的三角面总数量。

    关于 Tris 和 Verts

    1、 Camera 的渲染性能受到 Draw calls 的影响。之前说过,对一个物体进行渲染,会生成相应的 Draw call,处理一个 Draw Call 的时间是由它上边的 TrisCerts 数目决定。尽可能得合并物体,会很大程度的提高性能。举个很简单例子,比如场景一种有 1000 个不同的物体,每个物体都有 10 个 Tris; 场景二中有 10 个不同的物体,每个物体有 1000Tris 。 在渲染处理中,场景一中会产生 1000Draw Calls,它的渲染时间明显比场景二慢。

    2、Unity stats 视图中的 TrisVerts 并不仅仅是视锥中的梯形内的 TrisVerts,而是 Camerafield of view 所有取值下的 trisverts,换句话说,哪怕你在当前 game 视图中看不到这个 cube, 如果当你把 field of view 调大到179 过程中都看不到这个 cubestats 面板才不会统计, GPU 才不会渲染,否则都会渲染,而且 unity 不会把模型拆分,这个模型哪怕只有 1 个顶点需要渲染, Unity 也会把整个模型都渲染出来。(参考自Mess的《Unity Camera组件部分参数详解》)

    3、新建一个空的场景,里面没有添加任何物体,为什么 status 面板上显示有 1.7k Tris 以及 5.0k Verts 。这是因为空的场景自带默认的天空盒、Windows—>Lighting打开 Ligh下的 Scene 面板,把 Skybox 里的材质设为空。删掉它,就会发现 Tris 和 Verts 都会变为0了。

    这里写图片描述

    Scree
    获取当前 Game 屏幕的分辨率大小,后面的 值 表示总的内存使用。


    Shadow casters
    表示场景中有多少个 可以投射阴影的物体,一般这些物体都作为场景中的光源。

    Visible skinned meshed
    渲染皮肤网格的数量。

    Animations
    正在播放动画的数量。

    脚本获取值
    在编辑模式下这些数据是可以通过脚本获取到的,不过打出包来不太成,代码如下:

     GUILayout.TextField("Total DrawCall: " + UnityStats.drawCalls);
            GUILayout.TextField("Batch: " + UnityStats.batches);
            GUILayout.TextField("Static Batch DC: " + UnityStats.staticBatchedDrawCalls);
            GUILayout.TextField("Static Batch: " + UnityStats.staticBatches);
            GUILayout.TextField("DynamicBatch DC: " + UnityStats.dynamicBatchedDrawCalls);
            GUILayout.TextField("DynamicBatch: " + UnityStats.dynamicBatches);
    
            GUILayout.TextField("Tri: " + UnityStats.triangles);
            GUILayout.TextField("Ver: " + UnityStats.vertices);

    参考:http://blog.csdn.net/wdmzjzlym/article/details/51335915

    http://blog.csdn.net/fansongy/article/details/51025325

    展开全文
  • 文章目录1. 基础2. 实现窗口小部件(App Widget)第一步:创建窗口小部件布局文件第二步:添加`...这些视图在用户界面中称为“窗口小部件”,可以通过App窗口小部件提供程序发布窗口小部件。能够容纳其...
  • 腾讯云搭建Socks5多IP代理服务器实现游戏单窗口单IP腾讯云多IP Socks5搭建教程 配合代理工具实现 单窗口单IP1.多IP服务器选择2.服务器购买功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与...
  • 浏览器探究——多窗口

    千次阅读 2012-02-22 10:05:01
    浏览器探究——多窗口 点击网址导航栏后面的多窗口的图标。 会调用...通过名字可以看出这个导航栏是指针对手机的,因为4.0即包含phone的需求又包含pad的需求。NavigationBarPhone是继承自Navigation
  • 1.运行-cmd 进入command 窗口 首先cd 到mysql目录下的bin的 路运行-cmd 进入command 窗口 首先cd 到mysql目录下的bin的路径。注意cd D盘时直接输入D:就ok。其余的要 "cd bin".   2. 通常,登录数据库之后...
  • android浏览器研究-多窗口

    千次阅读 2014-10-29 13:26:00
    浏览器探究——多窗口 点击网址导航栏后面的多窗口的图标。 会调用...通过名字可以看出这个导航栏是指针对手机的,因为4.0即包含phone的需求又包含pad的需求。NavigationBarPhone是继承自Navigation
  • Android7.0多窗口实现原理(一)

    千次阅读 2018-07-24 20:57:05
    在Android N(7.0)版本开始,系统支持了多窗口功能。在有了多窗口支持之后,用户可以同时打开和看到多个应用的界面。并且系统还支持在多个应用之间进行拖拽。在大屏幕设备上,这一功能非常实用。 在Android N中...
  • 3.EWSTM8系列教程03_主窗口、工具栏的概述 4.EWSTM8系列教程04_菜单概述(一) 5.EWSTM8系列教程05_菜单概述(二) 6.EWSTM8系列教程06_工程节点选项配置(一) 7.EWSTM8系列教程07_工程节点选项配置(二) 8.....
  • 手机发送延迟长 数据吞吐量较低 3.从设备潜伏值小 从设备功耗高 从设备及时或更快速的收到主设备发送的数据 4.从设备潜伏值大 从设备在潜伏期期间功耗低 从设备无法及时或快速接收主...
  • 先上效果图:之前用手机QQ时,一直很觉得这个窗口提示挺不错的,今天将它大概地实现了一遍。首先是:提示窗口的三角下标是可以改变位置的,然后窗口中有很多小的item,item被点击时会显示出不同的颜色,同时三角下标...
  • 手机基础知识

    千次阅读 2018-06-27 20:21:07
     需要根据物理尺寸的大小准备5套布局,layout(放一些通用布局xml文件,比如界面中顶部和底部的布局,不会随着屏幕大小变化,类似windos窗口的title bar),layout-small(屏幕尺寸小于3英寸左右的布局),layout-...
  • 选择器主要功能是:通过输入MCU/BOARD型号,或者按照特定条件(如MCU系列、FLASH大小、外设控制器数量等)来筛选符合自己条件的MCU/BOARD。 这里的选择器可以当做STM32的选型工具。   1.MCU和BOARD选择...
  • ,现在如果我将该TCP流拆分了,拆成了n个流,那么当前链路上存在的TCP流的数量变成了 m+n 个,按照公平性,每一个流的带宽为 W/(m+n) ,其中 n*(W/(m+n)) 的带宽属于我。现在证明 n*(W/(m+n))>W/m  程序员不是数学...
  • 再议手机显示特效

    千次阅读 2011-08-21 23:49:18
    发现留言又有朋友问题我屏幕特效的算法了,其实之前我研究手机编程时已经写了很多手机显示特效方面的文章。我也曾经在一些文章中讲过一些线性函数如何应用在手机屏幕的变化中的文章,只不过文章都是点到即止,如果你...
  • 目录 腾讯云多IP 配合代理工具实现下图的功能 单窗口单IP 1.多IP服务器选择 ...7.socks5IP如何使用 实现 端游 模拟器 手机窗口单IP 7.1 万安挂机宝 下载后安装包里有介绍 7.2.proxydroid ...
  • #关闭当前窗口 driver.close() #输出主窗口句柄 print now_handle driver.switch_to_window(now_handle) #返回主窗口 开始下一个跳转 步骤如下:  1.首先,current_window_handle获取当前首页窗体并...
  • 窗口服务器道格拉斯.肥瑟451窗口服务器(或称WSERV)与symbian OS的几乎所有的部分协同工作,从核心到应用,只有通信子系统完全例外。窗口服务器的两个主要的职责是屏幕管理和事件管理。WSERV从内核接收到事件,然后...
  • 解决当弹窗口变小的时候变成1列问题  注意弹窗框的宽度 750px 应该用sm   网格选项 下表总结了 Bootstrap 网格系统如何跨多个设备工作:   超小设备手机(&lt;768px) 小型设备平板电脑...
  • 今年google 16 i/o 强势推出 android n ...会中提到android N 运行环境有很大提升,30%到600%提升。...OK、好东西用了才知道、那么如何运行Android N呢会学到什么多窗口布局互动 确保后台服务继续工作,而打盹模
  • 手机对话中的语音处理(一)

    千次阅读 2015-12-15 10:45:46
    尽管大多数人认为手机是传统有线电话服务的延伸,事实上,手机技术是极其复杂而且堪称神奇的技术。很少有人意识到这些小型设备为了维持一个电话谈话需要每秒执行数百万次的计算,如果我们细看将语音电子信号转换为...
  • Flink 是一个默认就有状态的分析引擎,前面的 WordCount 案例可以做到单词的数量的累加,其实是因为在内存中保证了每个单词的出现的次数,这些数据其实就是状态数据。但是如果一个 Task 在处理过程中挂掉了,那么它...
  • Vivo7.0以上手机,无法弹出自定义Toast(WindowManager通过addView方式,再配合动画),由于Android手机系统权限逐步收紧,导致无法成功申请的“SYSTEM_ALERT_WINDOW”权限。 解决思路:先看权限是否被收回,其次看...
  • 手机操作系统学习总结

    千次阅读 2016-07-30 16:52:00
    手机操作系统 手机操作系统主要应用在智能手机上。主流的智能手机有Google Android和苹果的iOS等。智能手机与非智能手机都支持JAVA,智能机与非智能机的区别主要看能否基于系统平台的功能扩展,非JAVA应用平台,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 28,749
精华内容 11,499
关键字:

如何关闭手机窗口数量