Android其他优化

WebView 性能优化

[[WebView 缓存池]]

  • 在客户端刚启动时,就初始化一个全局的 WebView 待用,并隐藏;当用户访问了 WebView 时,直接使用这个 WebView 加载对应网页,并展示。这种方法可以比较有效的减少初始化 WebView 的时间。
  • 主要的还是 h5那边的优化
  • Android 版本不同,采用了不同的内核,兼容性 crash
  • oom:WebView 动态加载。就是不在 xml 中写 WebView,写一个 layout,然后把 WebView add 进去。
  • 腾讯 X5内核相对于系统 webview 好的多。
  • 单/多进程化:webView 在独立的进程里面,那么 WebView 的进程崩溃不会影响到主进程运行;同时 WebView 的安全漏洞也很难影响到主进程;如果是多进程的话,可以使用 WebView 的容器池,有二次秒开的作用;不过缺点就是需要你做好和 WebView 的跨进程通讯了。

开一个 webview 的多进程 WebActivity ,里面放着另一个进程的 CommonWebFragment,完后把 URL 传过去就可以加载了。如果需要交互得用到 aidl 多进程通讯

unknown_filename.7

  • 网络优化:我们可以让 WebView 的 host 和客户端的 host 保持一致,那么就达到复用 DNS 缓存的效果;如果客户端有针对网络请求进行了优化,那么可以让 WebView 的全部网络请求托管给客户端
  • H5离线包:这个是手 Q 的 H5方案之一,让客户端提前去下载离线的 H5数据包,WebView 只需要加载本地 H5数据包即可,这么做不仅可以避免一些 http 的劫持,而且跳过了 WebView 的建立 TCP 连接和 H5、CCS 等数据下载的过程,直接开始 UI 渲染,大大提高了 WebView 的效率
  • 用户体验方面,可以在顶部显示一个一个 progress,显示网页加载进度,而不是一个白屏呈现给用户看。
  • 设置一个全局的 WebView,减少 WebView 初始化的时间,避免后续操作的堵塞

内存泄露

removeView、clearHistory、clearView、destroy、置为 null,各种 dialog dismiss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fun destroyWebView() {
bridgeWebView?.let {
try {
val parent = it.parent
parent?.let { p ->
(p as ViewGroup).removeView(bridgeWebView)
}
it.stopLoading()
it.settings.javaScriptEnabled = false
it.loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
it.clearHistory()
it.clearView()
it.removeAllViews()
fileJsController?.release()
it.destroy()
bridgeWebView = null
dialog?.dismiss()
activateDialog?.dismiss()
} catch (e: Exception) {
e.printStackTrace()
}
}
}

引擎初始化

WebView 在实例化的时候,需要先初始化 Chromium 引擎,而 Chromium 引擎又是一个重量级的组件,而且很多初始化的工作都需要在主线程中完成,这样就很容易造成主线程卡顿甚至 ANR。

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
public class ShadowWebView {

public static void preloadWebView(final Application app) {
try {
app.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
startChromiumEngine();
return false;
}
});
} catch (final Throwable t) {
Log.e(TAG, "Oops!", t);
}
}

private static void startChromiumEngine() {
try {
final long t0 = SystemClock.uptimeMillis();
final Object provider = invokeStaticMethod(Class.forName("android.webkit.WebViewFactory"), "getProvider");
invokeMethod(provider, "startYourEngines", new Class[]{boolean.class}, new Object[]{true});
Log.i(TAG, "Start chromium engine complete: " + (SystemClock.uptimeMillis() - t0) + " ms");
} catch (final Throwable t) {
Log.e(TAG, "Start chromium engine error", t);
}
}
}

ShadowWebView 通过 WebViewTransformer 在 Application onCreate () 回调中注入 ShadowWebView.preloadWebView (),当主线程 IDLE 时,启动 Chromium 引擎。

该方案的优点是无侵入、接入成本低,缺点是由于这种方式反射了非公开 API,可能存在兼容性问题。

过度绘制、布局优化

[[垂直同步vsync]]

Overdraw (过度绘制)

描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的 UI 结构里面,如果不可见的 UI 也在做绘制的操作,这就会导致某些像素区域被绘制了多次,可能出现卡顿现象,浪费大量的 CPU 以及 GPU 资源。也会出现耗电的问题。调试 GPU 过渡绘制— 显示过渡绘制区域

  • 颜色标识: 从好到差: 蓝-绿-淡红-红
  • 验收标准:
  1. 控制过度绘制为2x(绿色)
  2. 不允许存在4x(红色)过度绘制
  3. 不允许存在面积超过屏幕1/4区域的3x 过度绘制(淡红色区域)

处理方案:布局的优化

  1. 尽量重用一个布局文件,使用 include 标签,多个相同的布局可以复用
  2. 减少一个布局的不必要节点,减少层级,可以使用 sdk 提供的工具 Hierarchy Viewer 进行层级查看具体使用
  3. 尽量使用 view 自身的参数,例如:Button, 有一个可以把图绘制在左边的参数:android:drawableLeft
  4. 使用< ViewStub />标签来加载一些不常用的布局(懒加载),多状态,有数据,空数据,加载失败,加载异常,网络异常等。
  5. 使用< merge />标签减少布局的嵌套层次,根布局 content 就是个 fragment,如果自己就是 fragment,就可以使用 merge。merge 必须放在布局文件的根节点上。通常和 include 一起使用。
  6. 减少多余的 background
  7. 使用约束布局

unknown_filename.2

viewStub 子 view 必须有个父 view 包裹

1
2
3
4
5
6
7
8
var layout: View? = null
if (viewStub3.parent != null) {   
layout = viewStub3?.inflate()   
viewStub3.visibility = View.VISIBLE   
var voiceView =       
layout?.findViewById<InteractionVoicePlayerView>(R.id.voiceView)
}

ViewStub 原理
在布局层次中占据一个空白的位置,并在需要时通过动态替换成指定的布局
当 ViewStub 被添加到布局中时,它只占据非常少的内存,并且不会立即加载或渲染其布局资源。它只有在被显式调用 inflate () 方法或设置 visibility 为可见时,才会加载并填充其指定的布局资源。

【强制】
页面拥上的 View 越多, measure、 layout、 draw 所花费的时间就越久。要缩短这个时间,关键是保持 View 的树形结构尽量扁平,而且要移除所有不需要渲染的 View。理想情况下,总共的 measure, layout, draw 时间应该被很好的控制在 16ms 以内,以保证滑动屏幕时 UI 的流畅。

AsyncLayoutInflater

异步布局加载
AsyncLayoutInflater 的使用非常简单,就是把 setContentView 和一些 view 的初始化操作都放到了 onInflateFinished 回调中
有局限,所有构建的 View 中必须不能直接使用 Handler 或者是调用 Looper. myLooper (),因为异步线程默认没有调用 Looper. prepare (),可以优化

https://mp.weixin.qq.com/s/wmFBPYkwGh8ijQMODF27Fw

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncLayoutInflater(this).inflate(R.layout.activity_main,null, new AsyncLayoutInflater.OnInflateFinishedListener(){
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
setContentView(view);
rv = findViewById(R.id.tv_right);
}
});

}

APK 体积优化大小

Android-Notes/blogs/Android/性能优化/包体积优化.md at master · Omooo/Android-Notes

删、缩、挪
对于低端机来说,过多的 dex 文件编译会严重影响冷启动时间,应用在运行时,Resource 资源、Library 以及 Dex 类加载这些都会占用不少的内存。安装包在解压后,占用 ROM 空间可能会翻倍,对 ROM 空间占用也会有一定压力。

  • 保持良好的编程习惯,不要重复或者不用的代码,谨慎添加 libs,移除使用不到的 libs。
  • native code 的部分,大多数情况下只需要支持 armabi 与 x86的架构即可。如果非必须,可以考虑拿掉 x86的部分。
  • 代码压缩与优化: 在打包编译的时候自动移除无用资源,使用 R8 进行无用代码移除,并使用 gradle 插件进行无用资源的移除。同时,可以考虑使用 Proguard 进行代码压缩和混淆,减少包体积。Debug 的时候不用混淆,因为会压缩,删除,编译变慢

使用 Lint 工具查找没有使用到的资源。去除不使用的图片,String,XML 等等。 assets 目录下的资源请确保没有用不上的文件。生成 APK 的时候,aapt 工具本身会对 png 做优化,但是在此之前还可以使用其他工具如 tinypng 对图片进行进一步的压缩预处理。 jpeg 还是 png,根据需要做选择,在某些时候 jpeg 可以减少图片的体积。可以使用 webp 图片。

有选择性的提供 hdpi,xhdpi,xxhdpi 的图片资源。建议优先提供 xhdpi 的图片,对于 mdpi,ldpi 与 xxxhdpi 根据需要提供有差异的部分即可。

尽可能的重用已有的图片资源。例如对称的图片,只需要提供一张,另外一张图片可以通过代码旋转的方式实现。
能用代码绘制实现的功能,尽量不要使用大量的图片。例如减少使用多张图片组成 animate-list 的 AnimationDrawable,这种方式提供了多张图片很占空间。

使用 svg 图片
(Scalable Vector Graphics),可缩放矢量图。SVG 不会像位图一样因为缩放而让图片质量下降。优点在于可以减小 APK 的尺寸。常用于简单小图标。
Android 中只支持 vector,我们可以通过 vector 将 svg 的根节点 svg 转换为 vector。
SVG 批量转换
java -jar svg2vector-cli-1.0.0. jar -d . -o a -h 20 -w 20
-d 指定 svg 文件所在目录
-o 输出 android vector 图像目录
-h 设置转换后 svg 的高
-w 设置转换后 svg 的宽
Android 5.0(API 21)之前的版本不支持矢量图,使用 Vector Asset Studio 有两种方式适配

图片优化: 将图片上传到服务器,并通过动态下载的方式减少包体积。这种方式适用于首次加载时依赖网络环境的情况。另外,可以采用图片着色器来处理同图不同色的情况,避免需要多张图片。

assets 目录下也可以挪
so 动态下发有可能下载失败,毕竟是少部分用户,不可能一直下载不下来。另外所有用户都有收益,那这个就值得做。

大多的 X86设备除了支持 X86类型的 SO 库,还兼容 ARM 类型的 SO 库,所以应用市场上大部分的 APP 只适配了 ARM 类型的 SO 库

  1. 判断目录中是否存在 so 文件, 存在则直接调用第三方 sdk
  2. 不存在 SO 文件从服务器下载相应的 so 库 (大的话可以压缩)
  3. 将 so 库复制到/data/data/packagename/…(context.getDir (“libs”, Context. MODE_PRIVATE))
  4. System.loadLibrary (xx)

Flutter 混合开发 - 动态下发 libflutter.so & libapp.so - 掘金
https://juejin.cn/post/7313446602441785382?utm_source=gold_browser_extension

其他

AndResGuard

目前 R8 已经支持资源路径混淆,并且也支持白名单配置。AndResGuard 可能要退出历史舞台了。

resource. arsc – > Android Resource

  • andresguard,路径变成了 r/d/a,在 AndResGuard 中,我们支持针对 resources. arsc、PNG、JPG 以及 GIF 等文件的强制压缩。资源混淆,res 下的图片名字也改了
  • 用的7z,有着更好的压缩率

res 变成了 r
unknown_filename.8
原理:
unknown_filename.9

电量优化

耗电优化的第一个方向是优化应用的后台耗电。因为用户最容易感知这个,我明明没有怎么打开,为什么耗这么多?在后台不要做这些:长时间获取 WakeLock (及时释放)、WiFi 和蓝牙的扫描、GPS、video、audio

  • 减少 cpu 占用率
  • 网络传输:设备以最大的传输速率进行操作。
  • 数据压缩:缩减传输时间,降低电量消耗
  • 选择更快的传输方式:虽然4G 芯片比 Wifi 芯片耗电低,但 Wifi 的速率可以让数据在较短时间内完成传输,从而降低电量消耗。
  • 请求集中发送
  • gps 的使用:不同的场景以及不同类型的 App 对定位更加需要个性化的区分。即时注销定位监听
  • 谨慎使用 WakeLock:Wake Lock 是一种锁的机制,只要有人拿着这个锁,系统就无法进入休眠。一些 App 为了能在后台持续做事情,就会持有一个 WakeLock
  • 动态注册广播
  • 使用 JobScheduler,一些任务通过 JobScheduler 来触发,例如可推迟的网络请求、下载、GPS 等,可以在特定场景:连接 Wifi、连接电源等场景触发。既完成了任务,也无需考虑由于一些任务导致的电量消耗。(监听网络环境改变)

Android 最近几版的特色主要集中在省电和后台管理上,O 的发布,对 Service 和 Broadcast 又近一步加强了管束。主要可概括为如下两点:

  1. 后台应用不被允许创建后台服务,必须通过 JobScheduler 或者 Context.startForegroundService ()进行创建
  2. 特定的隐式广播不再被允许启动,必须通过 JobScheduler 调用或者显式注册的方式才能启动

JobSchedule 的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。这样做有两个好处:避免频繁的唤醒硬件模块,造成不必要的电量消耗以及避免在不合适的时间 (例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量。

  • 使用 Battery History
1
2
3
4
5
6
7
8
9
10
adb kill-serveadb devices adb start-server
adb shell dumpsys batterystats --enable full-wake-historyadb shell dumpsys batterystats --reset

adb bugreport > bugreport. txtadb shell dumpsys batterystats > batterystats. txt
adb shell dumpsys batterystats > com. example. android. demo. app > batterystats. txt
7.0以上
adb bugreport bugreport. zip
D:\PycharmProjects\goProjects\src\github. com\google\battery-historian\
go run cmd/battery-historian/battery-historian. go http://localhost:9999/


Android其他优化
http://peiniwan.github.io/2024/04/b6942795cb6e.html
作者
六月的雨
发布于
2024年4月6日
许可协议