优化 Android 应用程序的提示和工具

已发表: 2022-03-11

Android 设备有很多内核,所以编写流畅的应用程序对任何人来说都是一项简单的任务,对吧? 错误的。 由于 Android 上的一切都可以通过多种不同的方式完成,因此选择最佳选项可能很困难。 如果你想选择最有效的方法,你必须知道幕后发生了什么。 幸运的是,您不必依赖自己的感觉或嗅觉,因为有很多工具可以通过测量和描述正在发生的事情来帮助您找到瓶颈。 适当优化和流畅的应用程序极大地改善了用户体验,同时也减少了电池消耗。

让我们先看一些数字来考虑优化的真正重要性。 根据 Nimbledroid 的一篇帖子,86% 的用户(包括我)在使用过一次后由于性能不佳而卸载了应用程序。 如果你正在加载一些内容,你有不到 11 秒的时间向用户展示它。 只有每三分之一的用户会给你更多的时间。 因此,您可能还会在 Google Play 上获得很多差评。

构建更好的应用程序:Android 性能模式

测试用户的耐心是卸载的捷径。
鸣叫

每个用户一遍又一遍地注意到的第一件事是应用程序的启动时间。 根据 Nimbledroid 的另一篇文章,在 100 个顶级应用程序中,40 个在 2 秒内启动,70 个在 3 秒内启动。 因此,如果可能的话,您通常应该尽快显示一些内容,并稍微延迟背景检查和更新。

永远记住,过早的优化是万恶之源。 您也不应该在微优化上浪费太多时间。 您将看到优化经常运行的代码的最大好处。 例如,这包括onDraw()函数,它每帧运行一次,理想情况下每秒运行 60 次。 绘图是目前最慢的操作,因此请尝试仅重绘您必须做的事情。 更多关于这方面的内容将在稍后介绍。

性能提示

足够的理论,如果性能对您很重要,这里列出了您应该考虑的一些事项。

1. String 与 StringBuilder

假设您有一个字符串,出于某种原因,您想将更多的字符串附加到它 10,000 次。 代码可能看起来像这样。

 String string = "hello"; for (int i = 0; i < 10000; i++) { string += " world"; }

您可以在 Android Studio Monitors 上看到某些字符串连接的效率有多低。 有大量垃圾收集 (GC) 正在进行。

字符串与字符串生成器

这个操作在我相当不错的设备上大约需要 8 秒,它有 Android 5.1.1。 实现相同目标的更有效方法是使用 StringBuilder,就像这样。

 StringBuilder sb = new StringBuilder("hello"); for (int i = 0; i < 10000; i++) { sb.append(" world"); } String string = sb.toString();

在同一台设备上,这几乎是立即发生的,不到 5 毫秒。 CPU 和内存可视化几乎完全平坦,因此您可以想象这种改进有多大。 但请注意,为了实现这种差异,我们必须附加 10,000 个字符串,您可能不经常这样做。 因此,如果您只添加几个字符串一次,您将看不到任何改进。 顺便说一句,如果你这样做:

 String string = "hello" + " world";

它在内部转换为 StringBuilder,因此它可以正常工作。

您可能想知道,为什么第一种方式连接字符串这么慢? 这是由于字符串是不可变的,因此一旦创建,就无法更改。 即使你认为你正在改变一个字符串的值,你实际上是在用新的值创建一个新的字符串。 在如下示例中:

 String myString = "hello"; myString += " world";

你将在内存中得到的不是 1 个字符串“hello world”,而是 2 个字符串。 如您所料,字符串 myString 将包含“hello world”。 然而,原来的值为“hello”的字符串仍然存在,没有对其进行任何引用,等待被垃圾回收。 这也是为什么您应该将密码存储在 char 数组而不是 String 中的原因。 如果将密码存储为字符串,它将以人类可读的格式保留在内存中,直到下一次 GC 的时间长度不可预测。 回到上面描述的不变性,即使你在使用它之后给它分配另一个值,String 也会留在内存中。 但是,如果您在使用密码后清空 char 数组,它将从各处消失。

2. 选择正确的数据类型

在开始编写代码之前,您应该确定将用于集合的数据类型。 例如,您应该使用Vector还是ArrayList ? 好吧,这取决于您的用例。 如果你需要一个线程安全的集合,它一次只允许一个线程使用它,你应该选择一个Vector ,因为它是同步的。 在其他情况下,您可能应该坚持使用ArrayList ,除非您确实有特定的理由使用向量。

当您想要一个具有独特对象的集合时,情况如何? 好吧,您可能应该选择一个Set 。 它们不能按设计包含重复项,因此您不必自己处理。 有多种类型的集合,因此请选择适合您的用例的集合。 对于一组简单的唯一项目,您可以使用HashSet 。 如果要保留插入项目的顺序,请选择LinkedHashSetTreeSet自动对项目进行排序,因此您不必在其上调用任何排序方法。 它还应该有效地对项目进行排序,而无需考虑排序算法。

数据占主导地位。 如果您选择了正确的数据结构并将事物组织得很好,那么算法几乎总是不言而喻的。 数据结构,而不是算法,是编程的核心。
— Rob Pike 的 5 条编程规则

对整数或字符串进行排序非常简单。 但是,如果您想按某个属性对类进行排序怎么办? 假设你正在写一份你吃的饭菜的清单,并存储它们的名字和时间戳。 您将如何按时间戳从低到高对餐点进行排序? 幸运的是,这很简单。 在Meal类中实现Comparable接口并重写compareTo()函数就足够了。 要按时间戳从最低到最高对餐点进行排序,我们可以这样写。

 @Override public int compareTo(Object object) { Meal meal = (Meal) object; if (this.timestamp < meal.getTimestamp()) { return -1; } else if (this.timestamp > meal.getTimestamp()) { return 1; } return 0; }

3. 位置更新

有很多应用程序可以收集用户的位置。 为此,您应该使用 Google Location Services API,它包含许多有用的功能。 关于使用它有单独的文章,所以我不再赘述。

我只想从性能的角度强调一些要点。

首先,只使用您需要的最精确的位置。 例如,如果您正在做一些天气预报,您不需要最准确的位置。 基于网络获得一个非常粗糙的区域更快,电池效率更高。 您可以通过将优先级设置为LocationRequest.PRIORITY_LOW_POWER来实现。

您还可以使用名为setSmallestDisplacement()LocationRequest函数。 如果它小于给定值,则以米为单位设置将导致您的应用程序不会收到有关位置更改的通知。 例如,如果您有一张附近附近餐馆的地图,并且您将最小位移设置为 20 米,那么如果用户只是在房间里走动,应用程序将不会请求检查餐馆。 这些请求将毫无用处,因为无论如何附近不会有任何新的餐厅。

第二条规则是仅在您需要时才请求位置更新。 这是不言自明的。 如果您真的在构建该天气预报应用程序,则无需每隔几秒钟请求一次位置,因为您可能没有如此精确的预报(如果有,请联系我)。 您可以使用setInterval()函数来设置设备更新您的应用程序有关位置的所需时间间隔。 如果多个应用程序不断请求用户的位置,则每个应用程序都会在每次新的位置更新时收到通知,即使您设置了更高的setInterval()也是如此。 为防止您的应用收到过于频繁的通知,请确保始终使用setFastestInterval()设置最快的更新间隔。

最后,第三条规则是仅在您需要时才请求位置更新。 如果您每隔 x 秒在地图上显示一些附近的对象并且应用程序在后台运行,则您不需要知道新位置。 如果用户无论如何都看不到它,则没有理由更新地图。 确保在适当的时候停止监听位置更新,最好是在onPause()中。 然后,您可以在onResume()中恢复更新。

4. 网络请求

您的应用很有可能正在使用互联网下载或上传数据。 如果是,您有几个理由要注意处理网络请求。 其中之一是移动数据,它仅限于很多人,你不应该浪费它。

第二个是电池。 如果使用过多,WiFi 和移动网络都会消耗大量数据。 假设您要下载 1 kb。 要发出网络请求,您必须唤醒蜂窝或 WiFi 无线电,然后才能下载数据。 但是,收音机不会在手术后立即进入睡眠状态。 它将保持相当活跃的状态大约 20-40 秒,具体取决于您的设备和运营商。

网络请求

所以你能对它做点啥? 批。 为了避免每隔几秒钟就唤醒收音机,请预取用户在接下来的几分钟内可能需要的东西。 批处理的正确方法是高度动态的,具体取决于您的应用程序,但如果可能,您应该在接下来的 3-4 分钟内下载用户可能需要的数据。 还可以根据用户的互联网类型或充电状态编辑批处理参数。 例如,如果用户在充电时使用 WiFi,与用户使用电池电量不足的移动互联网相比,您可以预取更多的数据。 考虑所有这些变量可能是一件困难的事情,只有少数人会这样做。 幸运的是,有 GCM 网络管理器来救援!

GCM 网络管理器是一个非常有用的类,具有许多可自定义的属性。 您可以轻松安排重复任务和一次性任务。 在重复任务中,您可以设置最低和最高重复间隔。 这不仅允许批处理您的请求,还允许批处理来自其他应用程序的请求。 收音机每隔一段时间只能被唤醒一次,当它启动时,队列中的所有应用程序都会下载并上传它们应该做的事情。 这个Manager也知道设备的网络类型和充电状态,所以你可以做相应的调整。 您可以在本文中找到更多详细信息和示例,我敦促您检查一下。 示例任务如下所示:

 Task task = new OneoffTask.Builder() .setService(CustomService.class) .setExecutionWindow(0, 30) .setTag(LogService.TAG_TASK_ONEOFF_LOG) .setUpdateCurrent(false) .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) .setRequiresCharging(false) .build();

顺便说一句,从 Android 3.0 开始,如果你在主线程上进行网络请求,你会得到一个NetworkOnMainThreadException 。 那肯定会警告你不要再这样做了。

5. 反思

反射是类和对象检查它们自己的构造函数、字段、方法等的能力。 它通常用于向后兼容,以检查给定的方法是否可用于特定的操作系统版本。 如果您必须为此使用反射,请确保缓存响应,因为使用反射非常慢。 一些广泛使用的库也使用反射,例如用于依赖注入的 Roboguice。 这就是为什么您应该更喜欢 Dagger 2 的原因。有关反射的更多详细信息,您可以查看单独的帖子。

6. 自动装箱

自动装箱和拆箱是将原始类型转换为对象类型的过程,反之亦然。 在实践中,这意味着将 int 转换为 Integer。 为此,编译器在内部使用Integer.valueOf()函数。 转换不仅慢,对象也比它们的原始等价物占用更多的内存。 让我们看一些代码。

 Integer total = 0; for (int i = 0; i < 1000000; i++) { total += i; }

虽然这平均需要 500 毫秒,但重写它以避免自动装箱将大大加快速度。

 int total = 0; for (int i = 0; i < 1000000; i++) { total += i; }

该解决方案的运行时间约为 2 毫秒,速度提高了 25 倍。 如果你不相信我,你可以测试一下。 每个设备的数字显然会有所不同,但它仍然应该快得多。 这也是一个非常简单的优化步骤。

好的,您可能不会经常像这样创建 Integer 类型的变量。 但是更难避免的情况呢? 就像在地图中一样,您必须使用对象,例如Map<Integer, Integer> ? 看看许多人使用的解决方案。

 Map<Integer, Integer> myMap = new HashMap<>(); for (int i = 0; i < 100000; i++) { myMap.put(i, random.nextInt()); }

在地图中插入 100k 个随机整数大约需要 250 毫秒才能运行。 现在看一下 SparseIntArray 的解决方案。

 SparseIntArray myArray = new SparseIntArray(); for (int i = 0; i < 100000; i++) { myArray.put(i, random.nextInt()); }

这需要的时间要少得多,大约 50 毫秒。 它也是提高性能的更简单的方法之一,因为不需要做任何复杂的事情,而且代码也保持可读性。 虽然使用第一个解决方案运行一个清晰的应用程序占用了我 13MB 的内存,但使用原始整数占用了 7MB 以下的内存,所以只有一半。

SparseIntArray 只是可以帮助您避免自动装箱的很酷的集合之一。 像Map<Integer, Long>这样的映射可以被SparseLongArray替换,因为映射的值是Long类型。 如果您查看SparseLongArray的源代码,您会发现一些非常有趣的东西。 在引擎盖下,它基本上只是一对数组。 您也可以类似地使用SparseBooleanArray

如果您阅读了源代码,您可能已经注意到一条注释说SparseIntArray可能比HashMap慢。 我一直在尝试很多,但对我来说SparseIntArray在内存和性能方面总是更好。 我想这仍然取决于您选择哪个,试验您的用例,看看哪个最适合您。 使用地图时,一定要SparseArrays

7. OnDraw

正如我上面所说,当您优化性能时,您可能会看到优化经常运行的代码的最大好处。 经常运行的函数之一是onDraw() 。 它负责在屏幕上绘制视图,您可能不会感到惊讶。 由于设备通常以 60 fps 的速度运行,因此该功能每秒运行 60 次。 每帧有 16 毫秒的时间来完全处理,包括它的准备和绘制,所以你应该真正避免慢功能。 只有主线程可以在屏幕上绘制,所以你应该避免对其进行昂贵的操作。 如果您将主线程冻结几秒钟,您可能会看到臭名昭著的应用程序无响应 (ANR) 对话框。 对于调整图像大小、数据库工作等,请使用后台线程。

如果您认为您的用户不会注意到帧速率的下降,那您就错了!
鸣叫

我见过一些人试图缩短他们的代码,认为这样会更有效率。 这绝对不是要走的路,因为更短的代码并不意味着更快的代码。 在任何情况下,您都不应该通过行数来衡量代码的质量。

onDraw()中应该避免的一件事是分配像 Paint 这样的对象。 在构造函数中准备好所有东西,这样绘制的时候就准备好了。 即使您优化了onDraw() ,您也应该尽可能频繁地调用它。 有什么比调用优化函数更好的呢? 好吧,根本不调用任何函数。 如果你想绘制文本,有一个非常简洁的辅助函数叫做drawText() ,你可以在其中指定文本、坐标和文本颜色等内容。

8. ViewHolders

你可能知道这个,但我不能跳过它。 Viewholder 设计模式是一种使滚动列表更平滑的方法。 它是一种视图缓存,可以大大减少对findViewById()的调用,并通过存储视图来膨胀视图。 它可能看起来像这样。

 static class ViewHolder { TextView title; TextView text; public ViewHolder(View view) { title = (TextView) view.findViewById(R.id.title); text = (TextView) view.findViewById(R.id.text); } }

然后,在适配器的getView()函数中,您可以检查是否有可用的视图。 如果没有,您创建一个。

 ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, viewGroup, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.title.setText("Hello World");

你可以在互联网上找到很多关于这种模式的有用信息。 它也可以用于列表视图中包含多种不同类型元素的情况,例如某些部分标题。

9.调整图像大小

很有可能,您的应用将包含一些图像。 如果您从网上下载一些 JPG,它们可能具有非常大的分辨率。 但是,将显示它们的设备会小得多。 即使您使用设备的相机拍照,也需要在显示之前缩小它的尺寸,因为照片分辨率比显示器的分辨率大很多。 在显示图像之前调整图像大小是至关重要的。 如果您尝试以全分辨率显示它们,您很快就会耗尽内存。 在 Android 文档中有很多关于有效显示位图的文章,我会尝试总结一下。

所以你有一个位图,但你对它一无所知。 在您的服务中有一个名为 inJustDecodeBounds 的有用的位图标志,它允许您找出位图的分辨率。 假设您的位图为 1024x768,而用于显示它的 ImageView 仅为 400x300。 您应该继续将位图的分辨率除以 2,直到它仍然大于给定的 ImageView。 如果这样做,它会将位图缩小 2 倍,为您提供 512x384 的位图。 下采样位图使用的内存减少了 4 倍,这将极大地帮助您避免著名的 OutOfMemory 错误。

既然你知道该怎么做,你就不应该这样做。 … 至少,如果您的应用严重依赖图像,并且不仅仅是 1-2 张图像,则不会。 绝对避免手动调整大小和回收图像之类的东西,为此使用一些第三方库。 最受欢迎的是 Square 的 Picasso、Universal Image Loader、Facebook 的 Fresco,或者我最喜欢的 Glide。 它周围有一个庞大的活跃开发者社区,因此您也可以在 GitHub 的问题部分找到很多乐于助人的人。

10.严格模式

严格模式是一个非常有用的开发者工具,很多人都不知道。 它通常用于检测来自主线程的网络请求或磁盘访问。 您可以设置严格模式应该寻找什么问题以及它应该触发什么惩罚。 谷歌示例如下所示:

 public void onCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }

如果您想检测 Strict Mode 可以找到的每个问题,您也可以使用detectAll() 。 与许多性能提示一样,您不应盲目尝试修复严格模式报告的所有内容。 只需调查它,如果您确定这不是问题,请不要理会它。 还要确保仅将严格模式用于调试,并始终在生产版本中禁用它。

调试性能:专业的方式

现在让我们看看一些可以帮助您找到瓶颈的工具,或者至少可以显示出问题所在。

1.安卓监视器

这是一个内置在 Android Studio 中的工具。 默认情况下,您可以在左下角找到 Android Monitor,您可以在其中切换 2 个选项卡。 Logcat 和监视器。 Monitors 部分包含 4 个不同的图表。 网络、CPU、GPU 和内存。 它们是不言自明的,所以我将快速浏览它们。 这是在解析一些下载的 JSON 时所拍摄的图表的屏幕截图。

安卓监视器

网络部分以 KB/s 显示传入和传出流量。 CPU 部分以百分比显示 CPU 使用率。 GPU 监视器显示渲染 UI 窗口的帧所需的时间。 这是这 4 个监视器中最详细的监视器,因此如果您想了解更多详细信息,请阅读此内容。

最后,我们有内存监视器,您可能会使用最多。 默认情况下,它显示当前的可用和已分配内存量。 您也可以使用它强制进行垃圾收集,以测试使用的内存量是否下降。 它有一个名为 Dump Java Heap 的有用功能,它将创建一个 HPROF 文件,该文件可以使用 HPROF 查看器和分析器打开。 这将使您能够查看您分配了多少对象,哪些对象占用了多少内存,以及哪些对象可能导致内存泄漏。 学习如何使用这个分析器并不是最简单的任务,但它是值得的。 您可以使用内存监视器做的下一件事是进行一些定时分配跟踪,您可以根据需要启动和停止。 它在许多情况下可能很有用,例如在滚动或旋转设备时。

2.GPU过度绘制

这是一个简单的帮助工具,一旦您启用了开发者模式,您就可以在开发者选项中激活它。 选择Debug GPU overdraw,“Show overdraw area”,你的屏幕会出现一些奇怪的颜色。 没关系,这就是应该发生的事情。 颜色表示特定区域被透支的次数。 真彩色意味着没有透支,这是你应该瞄准的。 蓝色表示透支一,绿色表示二,粉红色三,红色四。

GPU 过度绘制

虽然看到真实的颜色是最好的,但您总会看到一些过度绘制,尤其是在文本、导航抽屉、对话框等周围。 所以不要试图完全摆脱它。 如果您的应用程序呈蓝色或绿色,那可能没问题。 但是,如果您在一些简单的屏幕上看到太多红色,您应该调查发生了什么。 如果您继续添加它们而不是替换它们,则可能有太多的碎片堆叠在一起。 正如我上面提到的,绘图是应用程序中最慢的部分,因此如果要在其上绘制超过 3 层,那么绘制东西是没有意义的。 随意使用它查看您最喜欢的应用程序。 您会看到即使下载量超过 10 亿的应用程序也有红色区域,所以当您尝试优化时,请放轻松。

3.GPU渲染

这是开发人员选项中的另一个工具,称为 Profile GPU 渲染。 选择它后,选择“在屏幕上作为条形图”。 您会注意到屏幕上出现了一些彩色条。 由于每个应用程序都有单独的栏,奇怪的是状态栏有自己的栏,如果你有软件导航按钮,它们也有自己的栏。 无论如何,当您与屏幕交互时,条形图会更新。

GPU 渲染

这些条由 3-4 种颜色组成,根据 Android 文档,它们的大小确实很重要。 越小越好。 在底部你有蓝色,它代表用于创建和更新视图的显示列表的时间。 如果这部分太高,说明有很多自定义视图绘制,或者在onDraw()函数中做了很多工作。 如果您使用的是 Android 4.0+,您会在蓝色条上方看到一个紫色条。 这表示将资源传输到渲染线程所花费的时间。 然后是红色部分,它表示 Android 的 2D 渲染器向 OpenGL 发出命令以绘制和重绘显示列表所花费的时间。 顶部是橙色条,表示 CPU 等待 GPU 完成工作的时间。 如果它太高,应用程序在 GPU 上做了太多的工作。

如果你足够好,橙色之上还有一种颜色。 它是代表 16 毫秒阈值的绿线。 由于您的目标应该是以 60 fps 的速度运行您的应用程序,因此您有 16 毫秒的时间来绘制每一帧。 如果你不成功,一些帧可能会被跳过,应用程序可能会变得生涩,用户肯定会注意到。 特别注意动画和滚动,这是最重要的平滑度。 即使您可以使用此工具检测到一些跳帧,但它并不能真正帮助您找出问题的确切位置。

4.层次查看器

这是我最喜欢的工具之一,因为它非常强大。 您可以从 Android Studio 通过 Tools -> Android -> Android Device Monitor 启动它,或者它也在您的 sdk/tools 文件夹中作为“monitor”。 您还可以在那里找到一个独立的 hierarachyviewer 可执行文件,但由于它已被弃用,您应该打开监视器。 无论您打开 Android 设备监视器,切换到 Hierarchy Viewer 透视图。 如果您没有看到分配给您的设备的任何正在运行的应用程序,您可以采取一些措施来修复它。 还可以尝试查看这个问题线程,有各种各样的问题和各种解决方案的人。 有些东西也应该对你有用。

使用 Hierarchy Viewer,您可以获得视图层次结构的非常简洁的概览(显然)。 如果您在单独的 XML 中查看每个布局,您可能很容易发现无用的视图。 但是,如果您继续组合布局,则很容易混淆。 像这样的工具可以很容易地发现,例如,一些 RelativeLayout,它只有 1 个孩子,另一个 RelativeLayout。 这使得其中一个可移动。

避免调用requestLayout() ,因为它会导致遍历整个视图层次结构,以找出每个视图应该有多大。 如果测量值有冲突,层次结构可能会被多次遍历,如果发生在某些动画中,肯定会跳过一些帧。 如果您想了解更多有关 Android 如何绘制视图的信息,可以阅读此内容。 让我们看一下 Hierarchy Viewer 中的一个视图。

层次结构查看器

右上角包含一个按钮,用于在独立窗口中最大化特定视图的预览。 在它下面,您还可以在应用程序中看到视图的实际预览。 下一项是一个数字,它表示给定视图有多少个子视图,包括视图本身。 如果您选择一个节点(最好是根节点)并按“获取布局时间”(3 个彩色圆圈),您将再填充 3 个值,同时出现标记为测量、布局和绘图的彩色圆圈。 测量阶段代表测量给定视图所花费的时间,这可能并不令人震惊。 布局阶段是关于渲染时间,而绘制是实际的绘制操作。 这些值和颜色是相互关联的。 绿色 1 表示视图呈现在树中所有视图的前 50% 中。 黄色表示在树中所有视图中较慢的 50% 中渲染,红色表示给定视图是最慢的视图之一。 由于这些值是相对的,所以总会有红色的。 你根本无法避免它们。

在值下,您有类名称,例如“TextView”、对象的内部视图 ID 和视图的 android:id,您在 XML 文件中设置。 我敦促您养成为所有视图添加 ID 的习惯,即使您没有在代码中引用它们。 这将使在 Hierarchy Viewer 中识别视图变得非常简单,并且如果您的项目中有自动化测试,它还将使定位元素更快。 这将为您和您的同事编写它们节省一些时间。 向 XML 文件中添加的元素添加 ID 非常简单。 但是动态添加的元素呢? 好吧,事实证明它也很简单。 只需在您的值文件夹中创建一个 ids.xml 文件并输入必填字段。 它看起来像这样:

 <resources> <item name="item_title" type="id"/> <item name="item_body" type="id"/> </resources>

然后在代码中,您可以使用setId(R.id.item_title) 。 再简单不过了。

在优化 UI 时,还有一些事情需要注意。 您通常应该避免深层次结构,而更喜欢浅层次,也许是宽层次。 不要使用不需要的布局。 例如,您可能可以将一组嵌套的LinearLayouts替换为RelativeLayoutTableLayout 。 随意尝试不同的布局,不要总是使用LinearLayoutRelativeLayout 。 还可以在需要时尝试创建一些自定义视图,如果做得好,它可以显着提高性能。 例如,您是否知道 Instagram 不使用 TextViews 来显示评论?

您可以在 Android Developers 网站上找到有关 Hierarchy Viewer 的更多信息,其中包含不同窗格的描述、使用 Pixel Perfect 工具等。我要指出的另一件事是在 .psd 文件中捕获视图,这可以通过“捕获窗口图层”按钮。 每个视图都将位于一个单独的图层中,因此在 Photoshop 或 GIMP 中隐藏或更改它非常简单。 哦,这是为每个视图添加 ID 的另一个原因。 这将使图层具有真正有意义的名称。

您会在开发人员选项中找到更多调试工具,因此我建议您激活它们并查看它们在做什么。 什么可能出错?

Android 开发者网站包含一组性能最佳实践。 它们涵盖了很多不同的领域,包括我还没有真正谈论过的内存管理。 我默默地忽略了它,因为处理内存和跟踪内存泄漏是一个完全不同的故事。 使用第三方库来有效地显示图像会有很大帮助,但如果您仍然有内存问题,请查看 Square 制作的 Leak canary,或阅读此内容。

包起来

所以,这是个好消息。 坏消息是,优化 Android 应用程序要复杂得多。 有很多方法可以做任何事情,因此您应该熟悉它们的优缺点。 通常没有任何银弹解决方案只有好处。 只有了解幕后发生的事情,您才能选择最适合您的解决方案。 仅仅因为您最喜欢的开发人员说某些东西很好,并不一定意味着它是最适合您的解决方案。 有更多的领域需要讨论,还有更多更高级的分析工具,所以我们下次可能会讨论它们。

确保您向顶级开发人员和顶级公司学习。 您可以在此链接中找到数百个工程博客。 这显然不仅仅是 Android 相关的东西,所以如果你只对 Android 感兴趣,你必须过滤特定的博客。 我强烈推荐 Facebook 和 Instagram 的博客。 尽管 Android 上的 Instagram 用户界面存在问题,但他们的工程博客有一些非常酷的文章。 对我来说,很容易看到每天处理数亿用户的公司是如何完成事情的,所以不阅读他们的博客似乎很疯狂,这真是太棒了。 世界变化非常快,所以如果你不不断地尝试改进、向他人学习和使用新工具,你就会被甩在后面。 正如马克吐温所说,不读书的人比不读书的人没有优势。