目录

文章会依照如下目录来整理优化点,也算是给自己挖个坑,以后有新的优化点会逐步补充进来,持续更新,也欢迎大家查漏补缺(´・ω・`)

  • 启动加速
  • 内存相关
  • 渲染相关
  • 编码相关
  • 非性能优化点
  • 性能调试工具篇

启动加速

Splash页面

从应用启动到完全渲染出第一个页面,中间的加载时间应用会呈现白屏或黑屏状态,Material Design设计规范建议我们应该给这段时间设置一张闪屏图(即占位图),让用户感到整个启动过程“毫无违和感”
写一个有闪屏图作为背景主题

1
2
3
4
<style name="SplashTheme" parent="AppTheme">
<item name="android:windowBackground">@drawable/splash_bg</item>
<item name="android:windowIsTranslucent">false</item>
</style>

创建一个专门用于显示启动页的SplashActivity设置该主题

1
2
3
4
5
6
7
8
9
<activity android:name=".ui.main.activity.WelcomeActivity"
android:theme="@style/SplashTheme"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

初始化组件及三方库

ApplicationonCreate方法中我们通常会做一些基础组件或第三方库的初始化工作,过于繁重的加载任务会影响应用从启动到显示MainActivity的间隔时间,所以应该遵循如下原则:

  • 异步加载 可以在子线程中初始化的任务放到子线程中完成(new ThreadIntentService
  • 延迟加载 不可以在子线程中初始化,但不需要启动后立刻使用的库可以在MainActivity渲染完成后再加载,或给一个固定时间延迟加载
  • 懒加载 顾名思义,需要使用时才加载,不使用则不加载
  • 常规加载 如果必须在主线程中加载且要启动后需要立即生效的库,则在ApplicationonCreate方法中加载

异步加载

1
2
3
4
5
6
7
8
9
10
11
12
public class App extends Application{
@Override
public void onCreate() {
super.onCreate();
instance = this;
//初始化屏幕宽高
getScreenSize();
//在子线程中初始化
InitializeService.start(this);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class InitializeService extends IntentService {
private static final String ACTION_INIT = "initApplication";
public InitializeService() {
super("InitializeService");
}
public static void start(Context context) {
Intent intent = new Intent(context, InitializeService.class);
intent.setAction(ACTION_INIT);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_INIT.equals(action)) {
initApplication();
}
}
}
private void initApplication() {
//初始化日志
Logger.init(getPackageName()).hideThreadInfo();
//初始化错误收集
// CrashHandler.init(new CrashHandler(getApplicationContext()));
initBugly();
//初始化内存泄漏检测
LeakCanary.install(App.getInstance());
//初始化过度绘制检测
BlockCanary.install(getApplicationContext(), new AppBlockCanaryContext()).start();
//初始化tbs x5 webview
QbSdk.allowThirdPartyAppDownload(true);
QbSdk.initX5Environment(getApplicationContext(), QbSdk.WebviewInitType.FIRSTUSE_AND_PRELOAD, new QbSdk.PreInitCallback() {
@Override
public void onCoreInitFinished() {
}
@Override
public void onViewInitFinished(boolean b) {
}
});
}
private void initBugly() {
Context context = getApplicationContext();
String packageName = context.getPackageName();
String processName = SystemUtil.getProcessName(android.os.Process.myPid());
CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(context);
strategy.setUploadProcess(processName == null || processName.equals(packageName));
CrashReport.initCrashReport(context, Constants.BUGLY_ID, isDebug, strategy);
}
}

延迟加载

1
2
3
4
5
6
7
8
9
10
11
12
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
}
});

内存相关

  • Bitmap不再使用时没有及时recycler()导致的泄漏,关于这点可以用LruCache来辅助管理Bitmap,具体如何操作在以前的一篇文章ECardFlow踩坑记的最后有提及,这里就不赘述了,为了防止加载大量Bitmap导致的OOM,还要注意对Bitmap的inSampleSize缩放比例和decodeformat解码格式做调整

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(mContext.getResources(), mRes[position], options);
    int inSampleSize = Math.min(options.outWidth / width, options.outHeight / height);
    int dstSample = 1;
    if (inSampleSize > dstSample) {
    dstSample = inSampleSize;
    }
    options.inJustDecodeBounds = false;
    //根据实际需要的图片大小来加载,当原图过大时会按比例缩放,而不是直接加载原图浪费内存空间
    options.inSampleSize = dstSample;
    //RGB_565、ARGB_4444等略微牺牲一些清晰度比ARGB_8888占用更少的空间
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    return BitmapFactory.decodeResource(mContext.getResources(), mRes[position], options);
  • 某些场景考虑使用applicationContext代替activityContext,尤其是将引用传入单例\静态对象时,防止Activity被持有引用无法回收

  • 善用软引用弱引用,一句话概括四种引用的话:
    • 强引用,JVM宁可OOM也不会主动回收的引用
    • 软引用SoftReference<T>,内存不足时才回收的引用
    • 弱引用WeakReference<T>,GC时只要发现了就会被回收的引用
    • 虚引用PhantomReference<T>,形同虚设,随时可能被回收的引用
  • 非静态内部类或匿名类会默认持有外部类的引用,和外部类无关时应该定义成static静态内部类,如下使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    private static class MyHandler extends Handler {
    WeakReference<ECardFlowLayout> mLayout;
    MyHandler(ECardFlowLayout layout) {
    mLayout = new WeakReference<>(layout);
    }
    @Override
    public void handleMessage(Message msg) {
    ECardFlowLayout layout = mLayout.get();
    switch (msg.what) {
    case MSG_JUDGE_RESET:
    layout.judgeReset();
    break;
    }
    }
    }
  • 部分Android系统自带的服务类在注册使用完毕后需要反注册unregister(),Java文件流等资源型对象使用完毕后应该关闭close()

  • 单例对象往往是静态的,若向其中注册监听addListener传入对象,需要在使用结束后清除监听置null,防止持续持有引用
  • 及时清理集合类中不再使用的引用,否则集合会越来越大,如果集合是static的话会更严重
  • 部分第三方库为避免泄漏会提供类似Destroy()Recycler()方法,注意调用

渲染相关

  • 使用include标签复用布局,提高代码可读性
  • 使用merge标签合并最外层根布局,减少一层层级,通常配合include或自定义ViewGroup或布局顶点是FrameLayout且不需要设置background或padding等属性的场景
  • 使用ViewStub标签,在该视图需要加载时才加载,比渲染后再View.GONE更好
  • 当需要空视图填充空间时用<Space><View>更合适
  • RelativeLayout中子View的onMeasuer会执行两次,LinearLayout使用weight属性时也是,所以优先使用LinearLayoutFrameLayout完成布局,避免RelativeLayout层层嵌套
  • TextViewdrawableLeft等属性可以替代TextView+ImageView的组合
  • TextView使用\n换行,android:lineSpacingMultiplier控制行间距,SpannableString做文字图片混编或局部文字大小、颜色、点击事件等等的控制可以满足大多数场景,而不是使用多个TextView
  • 在合适的场景下尝试使用体积很小的WebP格式,支持拉伸的.9图SVG矢量图及其动画
  • 包含大量绘制的复杂动画使用SurfaceViewTextureView来完成
  • 无交互、纯展示的自定义控件效果,用自定义Drawable比自定义View更高效
  • 应用运行后打开手机设置中的gpu呈现模式分析,出现红色的地方说明发生了多层过度绘制,举个栗子即父布局设置了背景颜色,子布局及其子布局又设置了背景颜色,在同一片区域做了多次重复渲染,这时候就需要去排查问题了

编码相关

  • 使用在内存层面做过优化的ArrayMap/SparseArray代替HashMap
  • 避免在Android里面使用Enum,开销是final static的数倍
  • 大量字符串拼接时使用StringBuilder代替String,并发场景下使用StringBuffer
  • 使用基本类型时注意避免无意义的装箱拆箱
  • 善用经典算法,如快排法二分法等,减少时间复杂度
  • 反射Reflect只是黑科技,会增加性能负担,避免大量使用

其他非性能优化点

除了最直观的性能优化外,还有一些优化点也可以提升用户体验

  • APK安装包瘦身(方便下载或更新)
  • 电量优化(省电)
  • 网络流量优化(省流量)
  • 弱网络状态优化(渣网也能玩的溜)
  • 离线缓存优化(没网也能玩的溜)

工具篇

本篇只列举工具,起个索引作用,每种调试工具的具体用法已经有很多优秀的文章介绍过了,可以参考附上的链接

0. LeakCanary

Square公司开源的内存泄漏检测库,发生泄漏时会记录下泄漏的调用堆栈,可以非常方便的定位泄漏点
开源地址:LeakCanary

1. Strictmode

Android自带,可以在代码中开启,严格模式下规范了开发者的行为,若写出了违反严格策略的代码会给予警告
参考链接:StrickMode严苛模式检测耗时与内存问题

2. Lint

Android Studio自带,lint是多功能的静态检测扫描工具,可以发现的问题包括:

  • 缺少翻译的文本
  • 布局性能(无意义的嵌套等)
  • 未使用到的资源(代码、图片等)
  • 不一致的数组大小
  • 硬编码
  • 图标问题(缺少、重复、错误等问题)
  • 可用性问题(如未指定的文本字段的输入类型)
  • manifest文件的错误

参考链接:Android代码优化——使用Android lint工具

3. TraceView、Systrace

Android自带,可以方便的查看各线程的执行情况,以及采集CPU数据等等,最常用于追踪每个方法的执行时间,定位耗时点,进而优化
参考链接:TraceView 分析图怎么看

4. HierarchyViewer

SDK自带,可查看布局层次结构,View绘制时耗时
参考链接:Android UI 优化——使用HierarchyViewer工具

5. Allaction Tracing

DDMS自带,执行某步操作后,详细的看到内存发生的变化
参考链接:Allaction Tracing追踪内存分配的轨迹

OK收工~ 滚去睡觉。。(= ̄ω ̄=)

参考文章

性能调优・技术优化点
Android性能优化的方方面面
App性能优化系列结语篇
Android内存优化之OOM

声明:本站所有文章均为原创或翻译,遵循署名-非商业性使用-禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名(Est)及原贴地址