Android 性能优化

894 阅读3分钟

布局优化

  • 使用 RelativeLayout, ConstraintLayout 减少布局层级;
  • 使用 <include> 标签复用布局;
  • 使用 <merge> 标签减少布局嵌套层级;
    • <merge> 标签经典使用场景:
      • (1) 布局顶节点是 FrameLayout ,且不需要设置 background 或 padding 等属性,可以使用 <merge> 代替,因为 Activity 视图的 parent view 就是个 FrameLayout, 所以可以用 merge 消除一个 FrameLayout 节点。
      • (2) 某布局作为子布局被其他布局 include 时, 使用 <merge> 标签作为该布局的顶节点,这样在被引入时,顶节点会自动被忽略,而将其子节点全部合并到主布局中。
  • 使用 <ViewStub> 标签延迟加载,减少渲染元素;
    • <ViewStub> 和 View.GONE 的区别是: View.GONE 虽然隐藏不可见了,但是在加载它所在的布局时,就已 经添加到布局上了,而 ViewStub 只会在显示的时候才会渲染布局。
  • 移除控件不必要的 background,避免过度绘制;

渲染优化

  • 使用 clipRect() 减少自定义 View 过度绘制
  • 避免在 onDraw() 方法中创建对象

内存优化

  • Bitmap 尺寸压缩 (BitmapOptions.inSampleSize = 2)
  • Bitmap 质量压缩 (RGB_565 代替 ARGB_8888, BitmapOptions.inPreferredConfig = RGB_565)
  • Bitmap 内存复用 (BitmapOptions.inBitmap = originBitmap)
  • 大图局部加载 BitmapRegionDecoder
  • Handler 静态内部类 + 弱引用, 单例持 ApplicationContext,BroadcastReceiver 注销,cursor 关闭,Bitmap对象及时 recycle(), 避免内存泄漏

启动优化

  • 避免在 Application 的 onCreate 执行耗时操作,使用 IntentService 异步初始化第三方 SDK
  • Splash 页面设置 windowBackground 属性,避免启动出现黑白屏
  • SplashFragment 实现主页预加载 : 把 SplashActivity 改成 SplashFragment , 应用程序入口仍然是 MainActivity , 在MainActivity 中先展示 SplashFragment, 当 SplashFragment 显示完毕后再将它 remove 掉,同时在 SplashFragment 的 2s 倒计时时间内进行主页数据的网络请求,在 SplashFragment 显示完毕后,再显示出 activity_main 中主界面的布局,主界面布局可能比较复杂,为减少解析时间,可以采用 ViewStub 的形式进行懒加载。
// MainActivity.java
public class MainActivity extends AppCompatActivity {

    private MainPageHandler mainPageHandler = new MainPageHandler();
    private SplashHideRunnable splashHideRunnable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 一上来就展示 SplashFragment
        SplashFragment splashFragment = SplashFragment.newInstance();
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction transaction = fm.beginTransaction();
        transaction.replace(R.id.fl_splash, splashFragment);
        transaction.commit();


        // 同时主界面执行初始化任务
        ViewStub viewStub = findViewById(R.id.main_view_stub);
        View mainView = viewStub.inflate();
        

        splashHideRunnable = new SplashHideRunnable(this, splashFragment);
        getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {
                mainPageHandler.post(splashHideRunnable);
            }
        }, 2000);


    }

    private static class SplashHideRunnable implements Runnable {

        private WeakReference<AppCompatActivity> appCompatActivityWeakReference;
        private WeakReference<SplashFragment> splashFragmentWeakReference;

        SplashHideRunnable(AppCompatActivity appCompatActivity, SplashFragment splashFragment) {
            this.appCompatActivityWeakReference = new WeakReference<>(appCompatActivity);
            this.splashFragmentWeakReference = new WeakReference<>(splashFragment);
        }

        @Override
        public void run() {
            // 移除 Fragment
            SplashFragment splashFragment = splashFragmentWeakReference.get();
            AppCompatActivity activity = appCompatActivityWeakReference.get();
            FragmentManager fragmentManager = activity.getSupportFragmentManager();
            FragmentTransaction ft = fragmentManager.beginTransaction();
            ft.remove(splashFragment);
            ft.commit();
        }
    }


    static class MainPageHandler extends Handler {

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mainPageHandler != null) {
            mainPageHandler.removeCallbacks(splashHideRunnable);
        }
    }
}


// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ViewStub
        android:id="@+id/main_view_stub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/activity_main_layout" />

    <FrameLayout
        android:id="@+id/fl_splash"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


</FrameLayout>

APK 包体积优化

  • Lint 工具扫描工程资源,移除未使用的资源。
  • gradle 启动代码压缩混淆。
  • png 图片压缩。
  • 图片资源考虑使用 webP 格式。
  • 使用 Shape 代码代替资源图片。
  • 移除未使用的资源 resconfig "zh" , "zh_CN","en"
  • lib 下考虑只支持主流架构的 so ,移除不需支持的 mips, x86 类型

线程优化

  • 使用线程池,复用线程对象,避免频繁创建和销毁线程带来的性能消耗。同时线程池能够有效的控制线程的最大并发数,避免了线程因抢占系统资源从而导致阻塞现象。