Android性能优化之提高App启动速度和Splash页面设计

1 App的启动

1.1 启动方式

通常来说,在安卓中应用的启动方式分为两种:冷启动和热启动。

(1)冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
(2)热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。

1.2 特点

(1)冷启动:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。

(2)热启动:热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。

上面说的启动是点击app的启动图标来启动的,而另外一种方式是进入最近使用的列表界面来启动应用,这种不应该叫启动,应该叫恢复。

1.3 应用启动的流程

这里写图片描述

由图分析可总结Activity大概启动流程:启动虚拟机—>启动AMS —>通过Zygote创建ApplicationProcess进程–>Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。

2 测量一个应用的启动时间

2.1 测量方法1

adb shell am start -W [PackageName]/[PackageName.MainActivity]

执行成功后将返回三个测量到的时间:
(1)ThisTime:指当前指定的MainActivity的启动时间
(2)TotalTime:整个应用的启动时间,Application+Activity的使用的时间。
(3)WaitTime:一般比TotalTime大点,包括系统影响的耗时

2.2 测量方法2

我的另一篇文章:性能优化之卡顿分析-计算并优化内存抖动和耗时操作–第4部分 如何使用Traceview计算某个类的耗时+第5部分 利用loop()中打印的日志检测应用中的UI卡顿

@Override
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Debug.startMethodTracing("textracing");//开始

    sleep();
    login();

    Debug.stopMethodTracing();//结束
}

2.3 测量方法3

log会打印一串log如下(Displayed过滤):

ActivityManager﹕ Displayed xxx.xxx.xxx/TestActivity: +1s272ms (total +3s843ms)  

这里写图片描述
Android Activity页面加载时间性能分析,以及改进要点

2.3 测量结果

adb shell am start -W com.guesslive.caixiangji/com.guesslive.caixiangji.activity.MainActivity

(1)优化前

Activity: com.guesslive.caixiangji/.activity.MainActivity
ThisTime: 1363
TotalTime: 1363
WaitTime: 1391
Complete

(2)Application优化后

Activity: com.guesslive.caixiangji/.activity.BootActivity
ThisTime: 521
TotalTime: 521
WaitTime: 547
Complete

(3)MainActivity&SplashFragment 优化后

Activity: com.guesslive.caixiangji/.activity.MainActivity
ThisTime: 340
TotalTime: 340
WaitTime: 366
Complete

3 减少应用启动时的耗时

主要是在Application初始化 + MainActivity的界面加载绘制时间。
(1)不要在Application的构造方法、attachBaseContext()、onCreate()里面进行初始化耗时操作。使用IntentService代替初始化工作。
(2)MainActivity,由于用户只关心最后的显示的这一帧,对我们的布局的层次要求要减少,自定义控件的话测量、布局、绘制的时间。不要在onCreate、onStart、onResume当中做耗时操作。
(3)对于SharedPreference的初始化。因为他初始化的时候是需要将数据全部读取出来放到内存当中。
(4)SplashActivity和MainActivity合为一个优化。
(5)合理使用延迟加载DelayLoad。达到效果:应用已经启动并加载完成,界面已经显示出来了,然后我们再去做其他的事情

4 综合方案

4.1 Application

public class MainApplication extends Application {

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

        InitializeService.start(this);//启动服务执行耗时操作
    }
}
/**
 * 启动初始化Service
 */
public class InitializeService extends IntentService {

    // IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
    public static final String ACTION_INIT_WHEN_APP_CREATE = "com.guesslive.caixiangji.service.action.app.create";
    public static final String EXTRA_PARAM = "com.guesslive.caixiangji.service.extra.PARAM";

    public InitializeService() {
        super("InitializeService");
    }

    /**
     * 启动调用
     * @param context
     */
    public static void start(Context context) {
        Intent intent = new Intent(context, InitializeService.class);
        intent.setAction(ACTION_INIT_WHEN_APP_CREATE);
        context.startService(intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) {
                performInit(EXTRA_PARAM);
            }
        }
    }

    /**
     * 启动初始化操作
     */
    private void performInit(String param) {
        initImageLoader();//初始化图片加载控件
        initRealm();//初始化Realm数据库
        initUser();//初始化用户(Realm数据库)
        initPush();//初始化推送
        initTuSdk();//初始化图sdk
        initQiNiu();//初始化七牛
        initQiyu();//网易七鱼
        initLogger();//注释启动,打开屏蔽打印
    }
}

4.2 MainActivity&SplashFragment

(1)MainActivity.java

public class MainActivity extends FragmentActivity {

    private Handler mHandler = new Handler();
    private SplashFragment splashFragment;
    private ViewStub viewStub;

    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewStub = (ViewStub)findViewById(R.id.content_viewstub);

        //首先加载并显示splash页面
        splashFragment = new SplashFragment();
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.frame, splashFragment);
        transaction.commit();

        //1.判断当窗体加载完毕的时候,立马再加载真正的布局进来
        getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                // 开启延迟加载
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        viewStub.inflate();//将viewstub加载进来,记载完毕后控件使用正常

//                      iv = (ImageView) findViewById(R.id.iv);
                    }
                } );
            }
        });

        //2.判断当窗体加载完毕的时候执行,延迟一段时间做动画。
        getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                // 开启延迟加载,实现fragment里面的动画效果
                mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment) ,2000);
//              mHandler.post(new DelayRunnable());//不开启延迟加载
            }
        });

        //3.同时进行异步加载数据


    }

    static class DelayRunnable implements Runnable{
        private WeakReference<Context> contextRef;
        private WeakReference<SplashFragment> fragmentRef;

        public DelayRunnable(Context context, SplashFragment f) {
            contextRef = new WeakReference<Context>(context);
            fragmentRef = new WeakReference<SplashFragment>(f);
        }

        @Override
        public void run() {
            // 移除splash页面
            if(contextRef!=null){
                SplashFragment splashFragment = fragmentRef.get();
                if(splashFragment==null){
                    return;
                }
                FragmentActivity activity = (FragmentActivity) contextRef.get();
                FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
                transaction.remove(splashFragment);
                transaction.commit();
            }
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
    }
}

(2)activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.applicationstartoptimizedemo.MainActivity" >

    <ViewStub 
        android:id="@+id/content_viewstub"
        android:layout="@layout/activity_main_viewstub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:id="@+id/frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
        <!-- android:background="@mipmap/bg_boot" 设置背景图-->
    </FrameLayout>

</RelativeLayout>

(3)activity_main_viewstub.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ProgressBar
        android:id="@+id/progressBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitStart"
        android:src="@drawable/content" />

</FrameLayout>

(4)SplashFragment.java

public class SplashFragment extends Fragment {

    @Override
    @Nullable
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_splash, container,false);
    }
}

4.3 给Window加上背景避免白屏

(1)bg_splash.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 底层白色 -->
    <item android:drawable="@color/white" />

    <!-- 顶层Logo居中 -->
    <item>
        <bitmap
            android:gravity="center"
            android:src="@drawable/ic_github" />
    </item>
</layer-list>

(2)SplashTheme

<style name="SplashTheme" parent="AppTheme">
        <item name="android:windowBackground">@drawable/bg_splash</item>
</style>

(3)ActivityManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.guesslive.caixiangji">

    <application
        android:name=".application.MainApplication"
        android:allowBackup="true"
        android:hardwareAccelerated="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"  
        android:theme="@style/AppTheme">

        <activity
            android:name=".activity.MainActivity"
            android:screenOrientation="portrait"
            android:theme="@style/SplashTheme">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".service.InitializeService"
            android:exported="false"/>

    </application>

</manifest>

5 结语

实际场景可能远比这个复杂,这只是其中一种分析思路,还有其他思路欢迎私信。

6 参考链接

Android App优化之提升你的App启动速度之实例挑战

Android优化应用启动速度

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值