如何优化Androd App启动速度彩世界彩票注册平台官
分类:彩世界彩票注册平台官网

还是回到启动优化,既然里面涉及那么多问题,我们该如何具体优化呢?

总结

讲到这里,也就差不多了。最后再来一个总结。对于冷启动的分析,我们可以先通过systrace来分析,先有个大概的了解。在这个阶段,建议在关键的方法中打上自定义的标签。通过systrace大概知道哪些过程比较耗时的时候,就可以通过TraceView来分析了。这里再强调一遍,由于TraceView自身会对性能产生影响,所以它给出的时间并不准确,但这不妨碍我们进行分析,如果有必要,再在相应的地方打上标签再跑一次systrace就好了。

最后,冷启动优化是一个比较考验耐心的过程,不要指望着一次就能找出所有的问题来。并且就算找到了,有时候也是要根据产品的需求来做取舍的。

使用EventBus

适当地使用EventBus可以延后一些初始化。在需要的地方post一个事件,EventBus会通知注册过这些事件的地方,这样可以把一些初始化在真实需要的时候再post一个触发事件,然后延后初始化。

EventBus使用3步骤

  1. 定义事件:

    public static class MessageEvent { /* Additional fields if needed */ }
    
  2. 在需要的地方注册:
    可以指定线程模式 thread mode:

    @Subscribe(threadMode = ThreadMode.MAIN)  
    public void onMessageEvent(MessageEvent event) {/* Do something */};
    

    注册与反注册

     @Override
     public void onStart() {
         super.onStart();
         EventBus.getDefault().register(this);
     }
    
     @Override
     public void onStop() {
         super.onStop();
         EventBus.getDefault().unregister(this);
     }
    
  3. 发送事件:

     EventBus.getDefault().post(new MessageEvent());
    

更详细的使用参见 How to get started with EventBus in 3 steps.

专栏读者评价

前言

关于冷启动的优化方法,网上已经有很多的文章了,总结起来,大概有以下几种优化方式:

  1. 优化布局,这一步是最简单的,然而如果你的布局不是特别重的话其实优化后效果也不明显。
  2. 异步加载,现在一个app都会使用各种各样的第三方SDK,这些SDK大都需要在Application中去初始化,如果能让这些SDK在工作线程中去初始化的话,能减轻不少主线程的负担。
  3. 懒加载,常见的场景是ViewPager Fragment,或者是一些资源等到使用的时候再去加载。
  4. 预加载,如果有图片的话可以尝试着去预加载,不要等到布局的时候才去加载,这一步往往能够省出很多时间,因为解码图片是一个比较耗时的操作,特别是对于比较低端的机型。

道理大家都懂,但是怎么去分析,找出这些优化点出来,网上的文章还是比较少的,或者说得没有那么详细。所以,这篇文章主要是记录下最近几个月来,做冷启动优化的一些分析的思路,文章篇幅可能有点长,希望看完之后,对你能有一些帮助。

ViewStub 初始化延迟

对于一些只有在特定情况下才会出现的view,我们可以通过ViewStub延后他们的初始化。例如出于广告业务的需求,在有广告投放的时候需要在首页展示一个视频或者一个h5广告。由于视频控件以及webview的初始化需要耗费较长时间,我们可以使用ViewStub,然后在需要显示的时候通过ViewStub的inflate显示真正的view。例如在启动页的xml中某一段如下:

<com.example.ad.h5Ad.ui.H5AdWebView
    android:id="@ id/ad_web"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone" />

可以修改为:

<ViewStub
    android:id="@ id/ad_web_stub"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout="@layout/h5_ad_layout"/>

并新建一个h5_ad_layout.xml如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.ad.h5Ad.ui.H5AdWebView
        android:id="@ id/ad_web"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

</LinearLayout>

然后在代码中需要显示webview时进行inflate:

...
    private void setupView() {
        ...  
        mAdWebViewStub = (ViewStub) findViewById(R.id.ad_web_stub);
        mAdWebViewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                isAdWebStubInflated = true;
            }
        });
        ...
    }

    /**
     * 显示H5交互广告
     */
    private void showWebAd() {
        ...
        if (!isAdWebStubInflated) {
            View h5AdLayout = mAdWebViewStub.inflate();
            mAdWebView = (H5AdWebView) h5AdLayout.findViewById(R.id.ad_web);
        }
        ...
    }

《 Android 开发高手课》现在正在限时拼团中,拼团价 ¥79,原价 ¥99,已经有近 1W 开发加入学习,想认真学习 Android 开发的同学,请抓紧上车(方式:扫下方的二维码)

通过 TraceView 分析应用冷启动

通过Systrace,虽然我们可以大致知道是在哪个流程慢了,比如说bindApplicationActivityStart或者是整个布局绘制流程慢了。但我们能知道的,也仅限于此了。Systrace只是给我们指明了一个方向,除非我们在每一个方法上打上Systrace的标签,这样得到的trace就能看见这些方法的耗时情况了,但这样做可行性并不高。并且,这个想法,TraceView已经帮我们实现了。TraceView通过收集某个阶段所有函数的运行信息,最后通过图表的形式给我们展示出来。关于TraceView,可以看Android 性能优化:使用 TraceView 找到卡顿的元凶

虽然TraceView很强大,但它的代价就是会让系统变得很卡,因为它的运行时开销严重干扰了运行环境。你想想,TraceView本身的应用就是卡顿场景,然后再加上自身的卡顿,用了之后有时候都在怀疑到底是谁造成的卡顿了。这也是我以前一直拒绝使用TraceView的理由,但是在这一轮冷启动优化的过程中,它的强大,足以让我接受它的缺点。由于TraceView的使用会带来一定的卡顿,所以关于它的数据,大家参考一下,看一下时间的占比就好了,不要太纠结于它给出的真实时间,如果怀疑哪个方法有问题,可以利用Log打印时间或者Systrace来验证一下。

还记得上面在弹窗界面通过取巧的方式来让Systrace可以在冷启动过程中打印出我们的自定义标签吗?最开始我也是用这种思路来操作TraceView的,但后来发现行不通。好在TraceView也可以像Systrace一样通过标签来打点。并且不用像Systrace一样需要选中进程。直接运行app就可以了。

需要注意的是: TraceView默认的文件大小是8M,对于冷启动来说可能还不够,可以根据自己的情况修改文件大小,如指定文件大小为20M:

Debug.startMethodTracing("cold-start", 20 * 1024 * 1024);

这里简单写了一个例子,在MainActivity的onCreate方法中模拟一些耗时的操作。然后打上开始和结束到标签。

public class AppImpl extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // 启动TraceView
        Debug.startMethodTracing("cold-start");


    }

    @Override
    public void onCreate() {
        super.onCreate();

    }
}

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new LongTimeClass();

        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        String test = "";
        for (int i = 0; i < 500; i  ) {
            test  = i;
        }

    }

    private class LongTimeClass {

        public LongTimeClass() {
            for (int i = 0; i < 10000; i  ) {
                Log.d(TAG, "LongTimeClass: ");
            }
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 结束TraceView
        Debug.stopMethodTracing();
    }
}

冷启动该测试应用后,会在SD卡中生成一个cold-start.trace的文件,我们把它拉到电脑上,用DDMS打开它。

TraceView

无论是上面的时间轴,还是下面的方法区域,看起来都密密麻麻的,刚开始接触这个工具的时候看着就烦。

我们暂时不用看上面的时间轴,在下面的方法区域,点击Incl Cpu Time,根据这个时间来排序,看不懂这些指标的请先看参考文章。可以看到前面那几行都是android相关的,这些我们并不是很关心,直接往下看,或者在下面的搜索框那里输入我们关心的onCreate

TraceView

展开onCreate后,可以看到谁调用它的,以及它都调用了谁。这里有个比较奇怪的地方就是Thread.sleep(50)只占了0.146ms,但它实际上是会阻塞50ms的。下面的图中在后面有一段是空白的就是睡眠50ms。所以如果有一些操作太耗时我们也是可以通过时间线看出来的。

先来看耗时最长的LongTimeClass,点击它就会跳转到这个方法里面去,如图。

TraceView

从下面的区域可以看到,调用它的是MainActivity的onCreate方法,然后这个方法耗时最长的地方是在Log.d方法上面。看到后面的10000/10001了吗?这里表示LongTimeClass的构造方法里面调用了Log.d方法10000次,而Log.d方法总共被调用了100001次,还有一次是在其它地方,同样的,点击Log.d可以查看谁调用了它。

还有一点是,当我们刚才在onCreate那里点击LongTimeClass进行跳转的时候,上面的时间轴会刷新,并标记出时间段,如图上面标记出来的地方,仔细看,可以看到这一段区间下面被括号括起来来,同时,上面也有一段红色的标记。反过来,我们点击时间轴上的方法,下面的方法区域也会跳转到相应的地方。

这里为什么要在类的构造方法中模拟耗时操作也是有原因的。我们在冷启动的时候,什么东西都没有,因此每个对象都需要去重新创建,而在类的构造方法中,可能经常会有耗时的操作是我们容易忽略掉的。还记得上面onBindeViewHolder的例子吗?所以在冷启动阶段,我们要特别注意这一点。快速定位的方法就是先以Incl Cpu Time条件来排序,再通过搜索框,搜索<init>,这能把类的初始化给过滤出来,然后我们再看它的耗时情况。到了后面某个类不再耗时的时候也就不用继续搜索下去了。

看完LongTimeClass,我们再回到onCreate方法中去,可以看到对于String的操作也是比较耗时的,这里也是有优化空间的。

TraceView

上面的Log.d其实也代表着一种情况,就是它本身并不是耗时的操作,但是它被调用了很多次,或者是递归调用了很多次。这种情况我们可以通过Calls Recur Calls/Total条件来排序。

使用后台线程

在启动的过程中,尽量把能在后台做的任务都放到后台,可以使用以下几个方式来执行后台任务:

  • AsyncTask: 为UI线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。
  • HandlerThread: 为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。
  • ThreadPool: 把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。
  • IntentService: 适合于执行由UI触发的后台Service任务,并可以把后台任务执行的情况通过一定的机制反馈给UI。

彩世界彩票注册平台官网 1

参考资料

测量Activity 的启动时间
性能工具Systrace
Android 性能优化:使用 TraceView 找到卡顿的元凶
手把手教你使用Systrace(一)

启动闪屏主题设置

默认的启动闪屏是白色的,某些开发者会通过设置一个透明的启动闪屏主题来隐藏启动加载慢的问题,不过这种做法会影响用户体验。我们可以通过设置一个带logo的启动闪屏主题来让用户感受到在点击桌面图标后马上得到响应。不过这里需要注意启动闪屏主题不能使用很大的图片资源,因为加载这些资源本身也是耗时的。
  设置启动闪屏可以在第一个展示的Acitivty设置主题:

AndroidManifest.xml:

<activity
    android:name=".activity.DictSplashActivity"
    android:theme="@style/MyLightTheme.NoActionBar.FullScreen">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

styles.xml:

<style name="MyLightTheme.NoActionBar.FullScreen" parent="MyLightTheme.NoActionBar">
    <item name="android:windowBackground">@drawable/bg_launcher</item>
    <item name="android:windowFullscreen">true</item>
</style>

bg_launcher.xml:

<?xml version="1.0" encoding="utf-8"?><!--
  ~ @(#)bg_launcher.xml, 2017-02-06.
  ~
  ~ Copyright 2014 Yodao, Inc. All rights reserved.
  ~ YODAO PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  -->

<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:opacity="opaque">
    <!-- The background color, preferably the same as your normal theme -->
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/background_grey"/>
            <size android:height="640dp" android:width="360dp"/>
        </shape>
    </item>
    <!-- Your product logo - 144dp color version of your app icon -->
    <item>
        <bitmap
            android:gravity="bottom|center"
            android:src="@drawable/splash_bottom" />
    </item>
</layer-list>

效果如下:

彩世界彩票注册平台官网 2

启动闪屏主题.gif

《 Android 开发高手课》现在正在限时拼团中,拼团价 ¥79,原价 ¥99,已经有近 1W 开发加入学习,想认真学习 Android 开发的同学,请抓紧上车(方式:扫下方的二维码)

通过录像查看冷启动时间

如果有条件的话,可以用上高速摄像机来将App启动的过程给录下来,然后再通过逐帧回放来确定冷启动的时间。这种方法虽然比较准确,但是性价比太差了!
不过,好消息是,即使我们不用高速摄像机,也可以通过另外的途径来达到这个效果。那就是使用screenrecord命令。(这里参考的是测量Activity 的启动时间)

$ adb shell screenrecord --bugreport /sdcard/launch.mp4

启动命令之后,就可以启动我们的app了(在测试之前,最好将系统的所有动画都给关掉,这样能够不受窗口动画的干扰)。当界面显示出来后,终止掉录像。然后将文件拉到电脑上。

 $ adb pull /sdcard/launch.mp4

录像是有了,但却是正常速度播放的,所以我们还需要一个能逐帧查看的视频播放器,比如说Mac上的Quicktime,如果你的Mac带TouchBar的话,那你会发现TouchBar Quicktime简直是绝配。通过逐帧查看,找到启动的那一刻,然后记录下屏幕左上角的时间点,然后再找到界面显示出来的那一帧,两个时间相减就得到启动时间了。

解决问题

在过去几个月,我一直在填“启动优化”这个坑,也对应用的性能优化有了更深的体会。

通过 Systrace 分析应用冷启动

虽然用录屏的方式可以比较准确的获取到启动时间,可通过录屏我们还是不知道到底慢在哪。这时候就该Systrace上场了。不知道Systrace 是什么或者不知道怎么使用的可以看性能工具Systrace。

Systrace有命令行和图形界面两种使用方式,我个人比较喜欢图形界面的方式,因为不用去记住命令,所以这里以图形界面来举例说明。

由于是要测试冷启动,需要先杀掉进程,所以我们选中system_process进程,然后点击启动systrace的按钮,然后会出现一个弹窗。

Systrace

Systrace

Trace Buffer Size(kb): 一般默认就好,如果抓出来的trace出现了did not finish..之类的字样,适当调高这个值。
Enbale Application Traces from: 如果你使用了自定义的trace标签,那这里就需要选中你的进程,这样自定义标签才会生效。

关于下面的勾选项,选得越多,生成的trace越详细。设置完之后,记得先杀掉App进程,然后点击确定,再启动App,生成的trace用Chrome打开。

Systrace

从截图中可以大概看到几个过程:ActivityThreadMain -> bindApplication -> activityStart -> 布局绘制流程(头顶上有F的那几块)。整个冷启动的过程大概是356ms,点击每一个色块,比如截图中我点中的是activityStart,可以在下方到面板看到它的执行时间信息。

这些信息就是系统默认会帮我们打出来的,然而这些信息对于我们来说远远是不够的,好在Android提供了自定义标签。我们可以在感兴趣的阶段插入标签,比如说Application的onCreate方法,Activity的各个生命周期方法,RecyclerView的onCreateViewHolderonBindViewHolder这些关键的地方。

@Override
protected void onCreate(Bundle savedInstanceState) {
    // 插入自定义标签
    TraceCompat.beginSection("onCreate");

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 和beginSection对应上
    TraceCompat.endSection();
}

Systrace

插入标签后,在启动systrace的弹窗中需要选中我们要测试的进程。这里有个小问题就是冷启动需要杀掉进程,而杀掉进程后,在这里你是看不到你的进程的,所以我们可以取个巧,先选中进程,然后再杀掉进程,最后再启动,这样就能抓到我们自定义的trace了。

Systrace

再次看trace,可以看到在activityStart下面多了个我们自定义的onCreate

方法讲完了,举个例子先,我在项目中的onBindViewHolder打上了标签,得到的trace中有一帧是这样子的:

Systrace

发现了吗?同样都是onBindViewHolder,第一次跟后面七次的总时间加起来差不多,执行时间是50ms,占了这一帧的一半,说明这里肯定是有问题的。这就是systrace的好处,它能让我们很直观的发现到一些问题。同样的,利用systrace,我也发现了项目在Application的onCreate耗时太长,这点不通过systrace其实也很容易找出来,因为在这个回调里面都是一些初始化操作。相反,像上面的第一次onBindViewHolder耗时太长就比较难发现了,最后我查出来的原因是第一次调用时会用到一个Util类,它有一块静态的代码,在这里面去加载so库导致了耗时过长。

使用systrace分析冷启动的使用就到这里,关于它的使用还是要多积累经验才能得心应手。

另外,从systrace上,你会发现inflate一个布局是一个比较耗时的操作,并且如果布局里面有使用到图片资源的话还需要去解码这些资源,也都是耗时的操作。所以这其实也是一个优化点来着。

其他可以优化的细节

  • 减少广告等业务逻辑时间
      这里属于业务逻辑的优化,可根据不同的应用发掘可以缩短的等待时间。
  • SharePreferences中的commit改为apply
      SharePreferences的操作涉及文件的读写,最好尽量使用apply方法代替commit方法。apply方法会先将结果保存在内存的SharePreferences中并异步地更新SharePreferences文件
  • onPause不要执行太多任务
      在展示另一个Acitivty之前,需要经过上一个Acitvity的onPause()方法,因此在Activity的onPause()方法中不适合有耗时的工作。
  • ContentProvider不要做太多静态初始化以及在onCreate()中做耗时操作。
      因为ContentProvideronCreate()会在Application onCreate()之前调用。
  • 减少View层级
      减少View的层级可以有效避免过度绘制,减少不必要的绘制过程。
  • 注意内存抖动
      瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。
  • 用更快的方式获取信息,例如获取Webview UA
      获取Webview UA可以通过创建要给Webview然后获取setting中的UserAgent,不过为了获取UA而创建Webview是一个比较耗时的操作。我们可以在API17及以上的系统中通过WebSettings.getDefaultUserAgent(context)快速获取。
  • 尽量删除没必要的中间过渡Activity,减少Activity切换时间
      Activity的切换是比较耗时的,如果没有必要,我们可以将达到主要页面之前的Activity删除,或者修改成Fragment动态加入。

综合来看,在卡顿优化中提到“systrace 函数插桩”似乎是比较理想的方案,而且它还可以看到系统的一些关键事件,例如 GC、System Server、CPU 调度等。

通过Log查看冷启动时间

要优化应用的冷启动,首先,必须得先知道应用的启动时间。这里找到了三种方法可以大致获取到应用到冷启动时间。

对于应用的冷启动,我们可以通过Activitymanager打印出的log来大致判断

I ActivityManager: Displayed com.xxx.android/.app.ui.activity.MainActivity:  2s725ms

这个时间是从应用启动到第一次绘制之间的时间,但对于用户来讲,关心的是什么时候能够看到内容。所以这个时间并不是很准确。举个例子来说,我们的首页内容是展示图片,Activitymanager打印出来的时间是200ms,这真的是很快的启动速度了。但图片从加载到全部显示给用户的过程耗时是500ms,加起来就是700ms。这个时候你还能说你的启动速度很快吗?另外,这个信息还有一个最大的缺点就是,它只告诉你你的app有多慢,但却不告诉你慢在哪里。不知道问题所在,也就无从下手优化了。

【2】胡凯,2016.[Android性能优化典范

第6季]()
【3】TellH的博客,2016.实现类似微信Viewpager-Fragment的惰性加载,lazy-loading

启动优化的进阶方法

【1】胡凯,2016.[Android性能优化典范

第5季]()

彩世界彩票注册平台官网 3

App 启动流程分析

上一篇文章《如何统计Android App启动时间》我们定义了从用户角度上观察的启动时间。我们把这段时间再细分成两段,一段是从用户点击Launcher图标到进入第一个Acitivity的时间,另一段是从第一个Activity到最后首页Activity完全展示出来用户可进行操作的时间。在第一段时间中耗时的任务主要体现在Application的创建,第二段时间耗时主要是因为Activity的创建以及在最后首页Activity展示之前的业务流程。主要解决的思路有两个:一个是尽可能将初始化延后到真正调用的时候,另一个是尽可能将不是用户第一时间能体验的业务功能延后。经过对我们App的详细分析以及对业务的了解,可以通过以下一些方法来解决应用启动慢的问题。

启动过程分析

控制Static初始化范围

启动过程可能会用到一些Utils等工具类,这些类中包含了几乎整个项目需要使用到的工具。我们在优化的过程中发现某些Utils类中定义了静态变量,而这些静态变量的初始化会有一定耗时。这里需要注意可以把静态变量的初始化移到第一次使用的时候。这样可以避免在用到工具类的其他方法时提前做了没必要的初始化。例如一个Utils如下:

public class ExampleUtils {
    private static HeavyObject sHeavyObject = HeavyObject.newInstance(); //比较耗时的初始化
    ...
    public static void useHeavyObject() {
        sHeavyObject.doSomething();
    }

    /**
     * 
     * 启动过程中需要用到的方法
     */
    public static void methodUseWhenStartUp() {
      ...
    }
    ...
}

可以修改为:

public class ExampleUtils {
    private static HeavyObject sHeavyObject;
    ...
    public static void useHeavyObject() {
        if (sHeavyObject == null) {
            sHeavyObject = HeavyObject.newInstance(); //比较耗时的初始化
        }
        sHeavyObject.doSomething();
    }

    /**
     * 
     * 启动过程中需要用到的方法
     */
    public static void methodUseWhenStartUp() {
      ...
    }
    ...
}

彩世界彩票注册平台官网 4

Fragment懒加载

如果应用使用一层甚至几层ViewPager,然后为了让加载后Fragment不被销毁而改变了setOffscreenPageLimit()来缓存所有Fragment,那么ViewPager会一次性将所有Fragment进行渲染,如果Fragment本身又包含了耗时很长的初始化将严重影响App的启动速度。即使是使用默认设置setOffscreenPageLimit(1),也会加载前一页和后一页的Fragment。因此我们考虑需要对Fragment进行懒加载。这里可以使用两种方式来实现Fragment的懒加载。
  第一种方式是继承模式,通过继承懒加载Fragment基类,在得到用户焦点后再调用生命周期方法。具体实现如下:

/**
 * 使用继承方式实现的懒加载Fragment基类
 */
public abstract class InheritedFakeFragment extends Fragment {
    protected FrameLayout rootContainer;
    private boolean isLazyViewCreated = false;
    private LayoutInflater inflater;
    private Bundle savedInstanceState;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        this.inflater = inflater;
        this.savedInstanceState = savedInstanceState;
        rootContainer = new FrameLayout(getContext().getApplicationContext());
        rootContainer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        return rootContainer;
    }

    @Override
    public final void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser && !isLazyViewCreated && inflater != null) {
            View view = onLazyCreateView(inflater, rootContainer, savedInstanceState);
            rootContainer.addView(view);
            isLazyViewCreated = true;
            onLazyViewCreated(rootContainer, savedInstanceState);
        }
    }

    /**
     * 获取真实的fragment是否已经初始化view
     *
     * @return 已经初始化view返回true,否则返回false
     */
    @SuppressWarnings("unused")
    public boolean isLazyViewCreated() {
        return isLazyViewCreated;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isLazyViewCreated = false;
    }

    /**
     * 用于替代真实Fragment的onCreateView,在真正获取到用户焦点后才会调用
     *
     * @param inflater           - The LayoutInflater object that can be used to inflate any views in the fragment,
     * @param container          - If non-null, this is the parent view that the fragment's UI should be attached to. The fragment should not add the view itself, but this can be used to generate the LayoutParams of the view.
     * @param savedInstanceState - If non-null, this fragment is being re-constructed from a previous saved state as given here.
     * @return Return the View for the fragment's UI, or null.
     */
    protected abstract View onLazyCreateView(LayoutInflater inflater, ViewGroup container, @Nullable Bundle savedInstanceState);

    /**
     * 用来代替真实Fragment的onViewCreated,在真正获得用户焦点并且{@link #onLazyViewCreated(View, Bundle)}
     *
     * @param view               - The View returned by onCreateView(LayoutInflater, ViewGroup, Bundle).
     * @param savedInstanceState - If non-null, this fragment is being re-constructed from a previous saved state as given here.
     */
    protected abstract void onLazyViewCreated(View view, @Nullable Bundle savedInstanceState);

}

真正的Fragment需要继承InheritedFakeFragment,并将的onCreateViewonViewCreated方法修改为onLazyCreateViewonLazyViewCreated。修改如下图所示。

彩世界彩票注册平台官网 5

继承延迟加载Fragment对比.PNG

创建时直接new出来InheritedLazyFragment.newInstance("InheritedLazyFragment", position);

第一种方式是代理模式,先创建代理的Fragment,当代理Fragment得到用户焦点之后再将真实的Fragment加入其中。具体实现如下:

/**
 * 使用代理方式实现的懒加载Fragment基类
 */
public class ProxyFakeFragment extends Fragment {
    private static final String REAL_FRAGMENT_NAME = "realFragmentName";

    private String realFragmentName;

    private Fragment realFragment;

    private LayoutInflater inflater;
    private boolean isRealFragmentAdded = false;
    private boolean isCurrentVisiable = false;


    public ProxyFakeFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param realFragmentName 需要替换的真实fragment.
     * @return A new instance of fragment FakeFragment.
     */
    @SuppressWarnings("unused")
    public static ProxyFakeFragment newInstance(String realFragmentName) {
        ProxyFakeFragment fragment = new ProxyFakeFragment();
        Bundle args = new Bundle();
        args.putString(REAL_FRAGMENT_NAME, realFragmentName);
        fragment.setArguments(args);
        return fragment;
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param realFragmentName 需要替换的真实fragment.
     * @param bundle           放入真实fragment 需要的bundle
     * @return A new instance of fragment FakeFragment.
     */
    @SuppressWarnings("unused")
    public static ProxyFakeFragment newInstance(String realFragmentName, Bundle bundle) {
        ProxyFakeFragment fragment = new ProxyFakeFragment();
        Bundle args = new Bundle();
        args.putString(REAL_FRAGMENT_NAME, realFragmentName);
        if (bundle != null) {
            args.putAll(bundle);
        }
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (TextUtils.isEmpty(realFragmentName) && getArguments() != null) {
            realFragmentName = getArguments().getString(REAL_FRAGMENT_NAME);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        this.inflater = inflater;
        View view = inflater.inflate(R.layout.fragment_fake, container, false);
        setUserVisibleHint(isCurrentVisiable);
        return view;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        isCurrentVisiable = isVisibleToUser;
        if (TextUtils.isEmpty(realFragmentName) && getArguments() != null) {
            realFragmentName = getArguments().getString(REAL_FRAGMENT_NAME);
        }
        if (!TextUtils.isEmpty(realFragmentName) && isVisibleToUser &&
                !isRealFragmentAdded) {
            getRealFragment();
            if (inflater != null) {
                addRealFragment();
            }
        }
        if (isRealFragmentAdded) {
            realFragment.setUserVisibleHint(isVisibleToUser);
        }
    }

    /**
     * 获取对应的真正的fragment实体
     *
     * @return 真正的fragment实体
     */
    public Fragment getRealFragment() {
        if (TextUtils.isEmpty(realFragmentName) && getArguments() != null) {
            realFragmentName = getArguments().getString(REAL_FRAGMENT_NAME);
        }
        if (!TextUtils.isEmpty(realFragmentName) && realFragment == null) {
            try {
                realFragment = (Fragment) Class.forName(realFragmentName).newInstance();
                realFragment.setArguments(getArguments());
                return realFragment;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        } else if (realFragment != null) {
            return realFragment;
        } else {
            return null;
        }
    }

    private void addRealFragment() {
        if (realFragment != null) {
            getChildFragmentManager()
                    .beginTransaction()
                    .add(R.id.fake_fragment_container, realFragment)
                    .commit();
            getChildFragmentManager().executePendingTransactions();
            isRealFragmentAdded = true;
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (TextUtils.isEmpty(realFragmentName) && getArguments() != null) {
            realFragmentName = getArguments().getString(REAL_FRAGMENT_NAME);
        }
    }
}

使用这种代理的方式,并不需要对真实的Fragment做特殊的改动,只需要在创建的时候通过代理Fragment进行创建:

Bundle bundle = new Bundle();
bundle.putString(OriginFragment.FRAGMENT_MSG, "ProxyLazyFragment");
bundle.putInt(OriginFragment.FRAGMENT_POS, position);
return ProxyFakeFragment.newInstance(OriginFragment.class.getName(), bundle);

具体实现代码见github项目:shenguojun/LazyFragmentTest

以下看看不同方式对Fragment生命周期的影响。
先看正常的Fragment生命周期如下:

05-03 16:59:17.420 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , setUserVisibleHint: false
05-03 16:59:17.438 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onCreateView
05-03 16:59:17.439 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onViewCreated
05-03 16:59:17.439 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onActivityCreated
05-03 16:59:17.443 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onStart
05-03 16:59:17.444 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onResume
05-03 16:59:20.662 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , setUserVisibleHint: true
05-03 16:59:49.417 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , setUserVisibleHint: false
05-03 16:59:50.678 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onPause
05-03 16:59:50.678 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onStop
05-03 16:59:50.678 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onDestroyView

使用继承方式真实Fragment生命周期如下:

05-03 17:00:20.795 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , setUserVisibleHint: false
05-03 17:00:20.800 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onActivityCreated
05-03 17:00:20.801 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onStart
05-03 17:00:20.801 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onResume
05-03 17:00:22.365 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onLazyCreateView
05-03 17:00:22.366 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onLazyViewCreated
05-03 17:00:22.366 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , setUserVisibleHint: true
05-03 17:00:25.197 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , setUserVisibleHint: false
05-03 17:00:26.037 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onPause
05-03 17:00:26.037 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onStop
05-03 17:00:26.038 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onDestroyView

使用代理方式Fragment生命周期如下:

05-03 17:01:01.257 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , setUserVisibleHint: false
05-03 17:01:01.260 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onCreateView
05-03 17:01:01.260 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onViewCreated
05-03 17:01:01.260 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onActivityCreated
05-03 17:01:01.261 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onStart
05-03 17:01:01.261 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onResume
05-03 17:01:01.761 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , setUserVisibleHint: true
05-03 17:01:03.625 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , setUserVisibleHint: false
05-03 17:01:04.132 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onPause
05-03 17:01:04.133 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onStop
05-03 17:01:04.134 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onDestroyView

可以看出使用代理方式不改变Fragment的生命周期,但是使用继承方式改变了Fragment的调用顺序。两种方式的优缺点如下表:

实现方式 优点 缺点
继承方式 不需要改变创建及管理代码 onResume()等方法在真实的createView之前调用,生命周期与没延迟化之前有差异
代理方式 1. 不需要改变真实Fragment代码</br> 2. 生命周期没有变化 管理以及创建代码需要修改

效果如下:

彩世界彩票注册平台官网 6

fragment-lazy-load.gif

具体的优化方式,我把它们分为闪屏优化、业务梳理、业务优化、线程优化、GC 优化和系统调用优化。

参考

在拿到整个启动流程的全景图之后,我们可以清楚地看到这段时间内系统、应用各个进程和线程的运行情况,现在我们要开始真正开始“干活”了。

在上一篇文章《如何统计Android App启动时间》中我们探讨了如何统计Android App的启动时间,以及简要分析了App启动流程。这一篇文章主要讲如何在实战中提升Android App的启动速度。下面我们先回顾一下App的启动流程。转载请注明出处:Lawrence_Shen

既然首页显示那么慢,那我能不能把尽量多的工作都通过异步化延后执行呢?很多应用的确就是这么做的,但这会造成两种后果:要么首页会出现白屏,要么首页出来后用户根本无法操作。

后记

通过之前的分析以及这篇文章介绍的启动优化方法,我们词典的启动速度得到了50%的提升,有效地提升了用户体验。在以后的开发过程中,当涉及到启动流程的代码时需要格外谨慎,避免有耗时的操作加入。当然目前的词典启动速度还可以进一步优化,可以思考的方向一下几点:1. 进一步优化信息流布局,减少不必要的绘制;2. 深入探索第三方SDK带来的启动速度延迟并尝试优化;3. 获取更多实时广告的成功率并尝试去除实时广告逻辑。

启动问题分析

用户如果想打开一个应用,就一定要经过“启动”这个步骤。启动时间的长短,不只是用户体验的问题,对于淘宝、京东这些应用来说,会直接影响留存和转化等核心数据。对研发人员来说,启动速度是我们的“门面”,它清清楚楚可以被所有人看到,我们都希望自己应用的启动速度可以秒杀所有竞争对手。

第 19 讲 | 耗电优化:耗电的优化方法与线上监控

从启动流程的 4 个关键阶段,我们可以推测出用户启动过程会遇到的 3 个问题。这 3 个问题其实也是大多数应用在启动时可能会遇到的。

你可以先回忆一下“卡顿优化”提到的几种工具。Traceview 性能损耗太大,得出的结果并不真实;Nanoscope 非常真实,不过暂时只支持 Nexus 6P 和 x86 模拟器,无法针对中低端机做测试;Simpleperf 的火焰图并不适合做启动流程分析;systrace 可以很方便地追踪关键系统调用的耗时情况,但是不支持应用程序代码的耗时分析。

启动优化

启动速度优化的方法和卡顿优化基本相同,不过因为启动实在是太重要了,我们会更加“精打细算”。我们希望启动期间加载的每个功能和业务都是必须的,它们的实现都是经过“千锤百炼”的,特别是在中低端机上面的表现。

小编提示:《 Android 开发高手课》现在正在限时拼团中,拼团价 ¥79,原价 ¥99,已经有近 1W 开发加入学习,想认真学习 Android 开发的同学,请抓紧上车(方式:扫下方的二维码)

现在应用启动流程越来越复杂,闪屏广告、热修复框架、插件化框架、大前端框架,所有准备工作都需要集中在启动阶段完成。上面说的 T3 首页显示时间对于中低端机来说简直就是噩梦,经常会达到十几秒的时间。

很多应用把启动结束时间的统计放到首页刚出现的时候,这对用户是不负责任的。看到一个首页,但是停住十几秒都不能滑动,这对用户来说完全没有意义。启动优化不能过于 KPI 化,要从用户的真实体验出发,要着眼从点击图标到用户可操作的整个过程。

我以微信为例,用户从桌面点击图标开始,会经过 4 个关键阶段。

“工欲善其事必先利其器”,我们需要先找到一款适合做启动优化分析的工具。

当然高手可不是这么容易当的,仅仅完成这些就美滋滋地向老大汇报工作成果:“启动速度提升 30%,秒杀所有竞品好几条街”,启动优化工作就结束了吗?“还有什么方法可以做进一步优化吗?怎么证明你秒杀所有的竞品?如何在线上衡量启动优化的效果?怎么保障和监控启动速度是否变慢?”

如果我们禁用了预览窗口或者指定了透明的皮肤,那用户点击了图标之后,需要 T2 时间才能真正看到应用闪屏。对于用户体验来说,点击了图标,过了几秒还是停留在桌面,看起来就像没有点击成功,这在中低端机中更加明显。

除了上面这些常规的优化方法,我还有一些与业务无关的“压箱底”方法可以帮助加快应用的启动速度。当然有些方法会用到一些黑科技,它就像一把双刃剑,需要你做深入的评估和测试。

关于性能优化,推荐你看看我专栏里的这些内容:

在真正动手开始优化之前,我们应该先搞清楚从用户点击图标开始,整个启动过程经过哪几个关键阶段,又会给用户带来哪些体验问题。

大家好,我是极客时间《 Android 开发高手课》作者、前微信高级工程师张绍文。今儿和大家分享下 Android 应用启动优化的“秘籍”,在优化过程中你会遇到什么问题?该如何分析?又该如何解决?

未来移动开发无论是变成大前端还是 Flutter 的世界,性能、效率和架构都是永恒不变的主线。今天我们在 Android 开发打下的坚实基础,未来也会帮助我们更好地理解和深入新的开发模式或者新的系统。崩溃、内存、存储、渲染、I/O、网络...... 这些知识以及它们背后的底层原理依然还是非常重要的。

彩世界彩票注册平台官网 7

启动优化的过程,就像是一个知识爬坡的过程。我们不停地尝试往底层深入,希望去摘更高的果实。同样专栏也是希望帮你补充“爬坡”所需的知识,让你一步步进阶成为高手,并可以用高手的思维去寻找、解决开发中遇到的复杂问题。

我去年在极客时间推出的《 Android 开发高手课》专栏里,特意用两篇文章来讲清楚启动优化的解决方案,让你的应用启动做到了极致,保证启动优化成果是长期有效。同时我也希望通过这个专栏的学习,你可以从高质量应用、实现高效开发和架构演进这三个部分,来精进自我。专栏的彩蛋也非常的多,比如我精心设计的练习 Sample,认真学习做笔记的同学还有机会获得价值 ¥4800 的“全球大前端技术大会”门票。

本文由彩世界注册首页发布于彩世界彩票注册平台官网,转载请注明出处:如何优化Androd App启动速度彩世界彩票注册平台官

上一篇:港版支付宝AlipayHK打通跨境游:香港人民代表大会 下一篇:没有了
猜你喜欢
热门排行
精彩图文