jjzjj

移动端页面秒开优化总结

小肥羊冲冲冲 2023-03-28 原文

前言

  App优化,是一个工作、面试或KPI都绕不开的话题,如何让用户使用流畅呢?今天谨以此篇文章总结一下过去两个月我在工作中的优化事项到底有那些,优化方面还算小白,有不对的地方还望指出海涵, 该文章主要通过讲述Native跳转到Flutter界面秒开率提升

问题分析

  当你拿到反馈App页面渲染时间长的工单的时候,第一步想到的不应该是有那些那些方法可以降低耗时,我们应该根据自己的真实业务触发,第一步 验证 通过打点或者工具去验证这个问题,了解 一个页面打开耗时的统计方式分析一个打开耗时是由那些方面组成,通过那些技术手段去解决80%的问题,抓大放小去处理问题。

  通过工具分析启动链路耗时,发现部分必要接口RT时间较长,Flutter引擎冷启耗时较长和View渲染耗时为主要耗时项。接下来就围绕着三个大方面去做一些优化。

网络优化

   以Android 界面跳转链路来说 ,具体链路看下图(模拟数据 主要明白思想)

image.png

   看到串行,就知道这里肯定可以有文章做

image.png

  可以看到在网络请求可以提前到 Router环节去解析并进行预加载,并行的话可以优化 必要接口RT的时长,节省的时间在页面秒开链路中占比最多。

  在这里需要兼容网络返回较慢的情况,我们可以引入骨架图,提升上屏率。

数据预请求

Router和请求

  通过拦截路由地址,判断路径是否属于预请求白名单。如果匹配,进入预请求逻辑,发起网络拼接和请求,在获取到结果进行本地缓存,供消费界面去消费。因为考虑到网络返回如果慢与界面,可以提供回调,消费界面进来进行绑定。

端侧通讯

   由于Native 跳转到 Flutter ,所以这里需要借助 Channel来进行管道传递,这里我们没有使用MethodChannel 而是选择 可以Native主动通知Flutter 的EventChannel来接收消息。

public class EventChannelManager implements IFlutterProphetPlugin {
    private static Map<String, EventChannel.EventSink> cachedEventSinkMap = new HashMap<>();
    private static LinkedList<Object> dataList = new LinkedList<>();

    public final static String CHANNEL_REQUEST_PRE = "event_channel";

    private static EventChannelManager instance;

    public static EventChannelManager getInstance() {
        if (null == instance) {
            instance = new EventChannelManager();
        }
        return instance;
    }

    @Creator
    public static IFlutterProphetPlugin create() {
        return new EventChannelManager();
    }

    //初始化
    @Override
    public void initChannel(FlutterEngine engine) {
        try {
            EventChannel eventChannel_pre = new EventChannel(engine.getDartExecutor(), CHANNEL_REQUEST_PRE);
            eventChannel_pre.setStreamHandler(new ProphetStreamHandler(CHANNEL_REQUEST_PRE));
        } catch (Exception ex) {
            Log.e(TAG, "init channel err :" + ex.getMessage());
        }
    }

    //发送消息
    @Override
    public void sendEventToStream(String eventChannel, Object data) {
        synchronized (this) {
            try {
                EventChannel.EventSink eventSink = cachedEventSinkMap.get(eventChannel);
                if (null != eventSink) {
                    eventSink.success(data);
                } else {
                    dataList.add(data);
                }
            } catch (Exception ex) {
            }
        }
    }

    //关闭
    public void cancel(String eventChannel) {
        EventChannel.EventSink eventSink = cachedEventSinkMap.get(eventChannel);
        if (null != eventSink) {
            eventSink.endOfStream();
        }
    }

    public static class ProphetStreamHandler implements EventChannel.StreamHandler {
        private String eventChannel;

        public ProphetStreamHandler(String eventChannel) {
            this.eventChannel = eventChannel;
        }

        @Override
        public void onListen(Object arguments, EventChannel.EventSink events) {
            cachedEventSinkMap.put(eventChannel, events);
            if (dataList.size() != 0) {
                for (Object obj : dataList) {
                    events.success(obj);
                }
                dataList.clear();
            }
        }

        @Override
        public void onCancel(Object arguments) {
            cachedEventSinkMap.remove(eventChannel);
        }
    }

}

上述代码为通用EventChannel创建和发送消息工具类,接口不贴了....

缓存

  预请求模块中,如果网络请求结果成功,可以将结果写入缓存SDK中(可以根据缓存SDK策略,内存和磁盘缓存都做好处理)。结合缓存策略,再次进入界面即可先读取缓存数据上屏,通过顶部Load状态提醒用户 预请求的数据正在加载中,来缩短秒开时间。

端智能

  通过大数据和算法对用户习惯性的使用链路进行分析,判断用户下一个节点将会进入哪个界面,匹配到预请求白名单,也可以更早的进行预请求逻辑 (没有集团SDK支撑的话可以不列为主要优化方式)。

image.png

数据后带

  以自己维护的App来说,首屏商品列表会返回很多数据包括但不限于:商品Url、商品名称、价格等核心信息,在进入商品详情中,我们通常会把商品id发送到详情界面,并再次进行商品详情接口的请求,那么我们可以通过数据后带的方式,先让详情页核心数据显示出来,然后通过局部骨架图来等待详情信息的返回,感官上缩短界面等待时长。

数据延后

  首屏中还会有很多二级弹窗列表数据接口的请求,其实这里的接口可以通过延后的方式来加载并渲染出来,减少首屏刚开始的CPU使用,为核心View渲染让步,减少CPU竞争。

业务逻辑优化

  部分不重要接口除了可以延后处理外,还可以通过推动后端合理缩小数据结构,减少不必要的网络消耗产生。对于部分小量接口,可以通过搭车的方式 进行接口合并 一块返回,部分数据可能不需要实时更新的,可以减少不必要请求来进行优化。

布局优化

异步加载

  假设场景是搜索结果列表,我们可以在数据请求前置的同时,去异步 inflate 一些 recyclerview 的 itemview,渲染阶段就可以节约 createViewHolder 的时间。(这里只是进行一个场景举例,更多的使用方法和业务强耦合,需要自行分析和合理设计避免负向优化)

递进加载

  顾名思义,其实递进加载和数据延后请求原理相似,每个界面可能都会有重要View,以商品列表为例,我可能更希望商品列表数据先返回回来,其他的接口可以延后,提升界面渲染速度。

其他优化

TODO 文章不断完善中,文章思想比重较多,欢迎大佬们互相讨论

有关移动端页面秒开优化总结的更多相关文章

  1. ruby - 多次弹出/移动 ruby​​ 数组 - 2

    我的代码目前看起来像这样numbers=[1,2,3,4,5]defpop_threepop=[]3.times{pop有没有办法在一行中完成pop_three方法中的内容?我基本上想做类似numbers.slice(0,3)的事情,但要删除切片中的数组项。嗯...嗯,我想我刚刚意识到我可以试试slice! 最佳答案 是numbers.pop(3)或者numbers.shift(3)如果你想要另一边。 关于ruby-多次弹出/移动ruby​​数组,我们在StackOverflow上找到一

  2. ruby-on-rails - 如何重命名或移动 Rails 的 README_FOR_APP - 2

    当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?

  3. ruby-on-rails - rbenv:从 RVM 移动到 rbenv 后,在 Jenkins 执行 shell 中找不到命令 - 2

    我从Ubuntu服务器上的RVM转移到rbenv。当我使用RVM时,使用bundle没有问题。转移到rbenv后,我在Jenkins的执行shell中收到“找不到命令”错误。我内爆并删除了RVM,并从~/.bashrc'中删除了所有与RVM相关的行。使用后我仍然收到此错误:rvmimploderm~/.rvm-rfrm~/.rvmrcgeminstallbundlerecho'exportPATH="$HOME/.rbenv/bin:$PATH"'>>~/.bashrcecho'eval"$(rbenvinit-)"'>>~/.bashrc.~/.bashrcrbenvversions

  4. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  5. ruby - 在 ASP 页面上 Mechanize 中断 - 2

    require'mechanize'agent=Mechanize.newlogin=agent.get('http://www.schoolnet.ch/DE/HomeDE.htm')agent.clicklogin.link_withtext:/Login/然后我得到Mechanize::UnsupportedSchemeError。 最佳答案 Mechanize不支持javascript但您可以将搜索字段添加到表单并为其分配搜索词并使用mechanize提交表单form=page.forms.firstform.add_fie

  6. ruby-on-rails - prawnto 显示新页面时不会中断的表格 - 2

    我有可变数量的表格和可变数量的行,我想让它们一个接一个地显示,但如果表格不适合当前页面,请将其放在下一页,然后继续。我已将表格放入事务中,以便我可以回滚然后打印它(如果高度适合当前页面),但我如何获得表格高度?我现在有这段代码pdf.transactiondopdf.table@data,:font_size=>12,:border_style=>:grid,:horizontal_padding=>10,:vertical_padding=>3,:border_width=>2,:position=>:left,:row_colors=>["FFFFFF","DDDDDD"]pdf.

  7. ruby - 每个页面上的 Jekyll 分页 - 2

    据我们所知,Jekyll默认分页仅支持index.html,我想创建blog.html并在那里包含分页。有什么解决办法吗? 最佳答案 如果您创建一个名为/blog的目录并在其中放置一个index.html文件,那么您可以向_config.yml表示paginate_path:"blog/page:num"。不是使用根文件夹中的默认index.html作为分页器模板,而是使用/blog/index.html。分页器将根据需要生成类似/blog/page2/和/blog/page3/的页面。这将使您到达yourwebsite.com/b

  8. ruby-on-rails - RoR && "coming soon"页面 - 2

    我正在寻找一种简单的方法来为我在RubyonRails上的项目实现简单的“即将推出”(预启动)页面。用户应该能够留下电子邮件以便在项目启动时收到通知。有没有这样的插件\gem?或者我应该自己做... 最佳答案 LaunchingSoon是一个Rails插件。它还集成了MailChimp或Campaignmonitor. 关于ruby-on-rails-RoR&&"comingsoon"页面,我们在StackOverflow上找到一个类似的问题: https:/

  9. ruby - 如何让 GitHub 页面使用 master 分支? - 2

    我有一个使用Jekyll托管在GitHub上的静态网站。问题是,我真的不需要master分支,因为存储库唯一包含的是网站。这样我就必须gitcheckoutgh-pages,然后gitmergemaster,然后gitpushorigingh-pages。有什么简单的方法可以摆脱gh-pages分支并直接从master推送? 最佳答案 Theproblemis,Idon'treallyneedthemasterbranch,astheonlythingtherepositorycontainsisthewebsite.Isthere

  10. ruby - 如何设置 Mechanize 页面编码? - 2

    我试图通过点击一个链接获得一个带有ISO-8859-1编码的页面,所以代码类似于这样:page_result=page.link_with(:text=>'link_text').click到目前为止,我得到的结果编码错误,所以我看到的字符如下:'T�tulo:'insteadof'Título:'我尝试了几种方法,包括:使用代理在第一个请求中声明编码:@page_search=@agent.get(:url=>'http://www.server.com',:headers=>{'Accept-Charset'=>'ISO-8859-1'})说明页面本身的编码page_result.

随机推荐