本文概述
Android设备具有很多核心, 因此编写流畅的应用程序对任何人来说都是一项简单的任务, 对吧?错误。由于Android上的所有操作都可以通过许多不同的方式完成, 因此选择最佳选项可能很困难。如果你想选择最有效的方法, 则必须了解幕后情况。幸运的是, 你不必依靠自己的感觉或嗅觉, 因为那里有许多工具可以通过测量和描述发生的事情来帮助你找到瓶颈。适当优化和流畅的应用程序可以大大改善用户体验, 并减少电池消耗。
首先让我们看一些数字, 以考虑优化的真正重要性。根据Nimbledroid的帖子, 由于性能不佳, 仅使用一次后, 有86%的用户(包括我)已经卸载了应用程序。如果你要加载一些内容, 则不到11秒即可显示给用户。只有三分之一的用户会给你更多时间。因此, 你可能还会在Google Play上收到很多不好的评论。
测试用户的耐心性是卸载的捷径。
鸣叫
每个用户反复注意到的第一件事是应用程序的启动时间。根据Nimbledroid的另一篇文章, 在100个顶级应用程序中, 有40个在2秒内启动, 有70个在3秒内启动。因此, 如果可能的话, 通常应尽快显示一些内容, 并延迟后台检查和更新一些内容。
永远记住, 过早的优化是万恶之源。你也不应在微优化上浪费太多时间。你将看到优化经常运行的代码的最大好处。例如, 这包括onDraw()函数, 该函数每帧运行一次, 理想情况下每秒运行60次。绘制是目前最慢的操作, 因此请尝试仅绘制必需的内容。关于此的更多信息将在以后发布。
性能技巧
理论上足够多, 如果性能对你很重要, 这里列出了一些你应该考虑的事项。
1.字符串vs. StringBuilder
假设你有一个字符串, 并且出于某种原因, 你想向其追加1万次字符串。该代码可能看起来像这样。
String string = "hello";
for (int i = 0; i < 10000; i++) {
string += " world";
}
你可以在Android Studio监视器上看到一些String级联的效率如何。有大量垃圾收集(GC)正在进行。
在配备Android 5.1.1的相当不错的设备上, 此操作大约需要8秒钟。达到相同目标的更有效方法是使用StringBuilder, 如下所示。
StringBuilder sb = new StringBuilder("hello");
for (int i = 0; i < 10000; i++) {
sb.append(" world");
}
String string = sb.toString();
在同一设备上, 这几乎是立即发生的, 不到5ms。 CPU和内存的可视化几乎完全平坦, 因此你可以想象这种改进有多大。但是请注意, 为了实现这种差异, 我们必须添加一万个字符串, 你可能不经常这样做。因此, 如果只添加几个Strings, 你将看不到任何改善。顺便说一句, 如果你这样做:
String string = "hello" + " world";
它在内部转换为StringBuilder, 因此可以正常工作。
你可能想知道, 为什么串联字符串如此缓慢?这是由于字符串是不可变的, 因此一旦创建, 就无法更改它们。即使你认为自己正在更改字符串的值, 实际上也正在使用新值创建一个新的字符串。例如:
String myString = "hello";
myString += " world";
你在内存中得到的不是1个字符串” hello world”, 而是2个字符串。如你所料, 字符串myString将包含” hello world”。但是, 具有” hello”值的原始String仍然有效, 没有任何引用, 等待被垃圾回收。这也是为什么应将密码存储在char数组而不是String中的原因。如果将密码存储为字符串, 它将以人类可读的格式保留在内存中, 直到下一个GC出现不可预测的时间长度。回到上面描述的不变性, 即使你在使用字符串后为其分配了另一个值, 该字符串也将保留在内存中。但是, 如果在使用密码后清空char数组, 它将在所有地方消失。
2.选择正确的数据类型
在开始编写代码之前, 你应该确定将用于集合的数据类型。例如, 你应该使用Vector还是ArrayList?好吧, 这取决于你的用例。如果你需要一个线程安全的集合, 该集合只能一次允许一个线程使用它, 则应选择一个Vector, 因为它是同步的。在其他情况下, 除非确实有使用向量的特定原因, 否则你可能应该坚持使用ArrayList。
当你想要一个包含唯一对象的集合时, 情况又如何呢?好吧, 你可能应该选择一套。它们不能包含重复的设计, 因此你不必自己照顾。集合有多种类型, 因此请选择一个适合你的用例的集合。对于一组简单的唯一项, 可以使用HashSet。如果要保留插入项目的顺序, 请选择一个LinkedHashSet。 TreeSet自动对项目进行排序, 因此你不必调用任何排序方法。它也应该有效地对项目进行排序, 而无需考虑排序算法。
数据占主导。如果你选择了正确的数据结构并组织得当, 那么这些算法几乎总是不言而喻的。数据结构而非算法是编程的核心。
-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来实现。
你还可以使用LocationRequest函数setSmallestDisplacement()。如果设置为米, 则如果设置小于给定值, 则不会通知你的应用位置更改。例如, 如果你有一幅地图, 周围有附近的餐馆, 并且你将最小位移设置为20米, 那么如果用户只是在房间里走动, 该应用将不会发出检查餐馆的请求。这些请求将毫无用处, 因为无论如何附近都不会有新餐厅。
第二条规则是仅在需要时才请求位置更新。这是不言而喻的。如果你确实在构建该天气预报应用程序, 则无需每隔几秒钟就请求一次位置, 因为你可能没有这么精确的天气预报(如果需要, 请与我联系)。你可以使用setInterval()函数来设置设备更新位置相关信息所需的时间间隔。如果多个应用不断请求用户的位置, 则即使你设置了更高的setInterval(), 也会在每次新的位置更新时通知每个应用。为了防止你的应用经常收到通知, 请确保始终使用setFastestInterval()设置最快的更新间隔。
最后, 第三条规则是仅在需要时才请求位置更新。如果你每隔x秒在地图上显示一些附近的物体, 并且该应用程序处于后台运行状态, 则无需知道新位置。如果用户仍然看不到地图, 则没有理由更新地图。确保在适当的时候停止监听位置更新, 最好在onPause()中。然后, 你可以在onResume()中恢复更新。
4.网络请求
你的应用很有可能使用互联网来下载或上传数据。如果是这样, 你有几个理由要注意处理网络请求。移动数据就是其中之一, 它仅限于很多人, 你不应该浪费它。
第二个是电池。如果使用过多, WiFi和移动网络都可能消耗很多。假设你要下载1 kb。要发出网络请求, 你必须唤醒蜂窝或WiFi无线电, 然后才能下载数据。但是, 手术后收音机不会立即入睡。它将保持相当活跃的状态约20-40秒, 具体取决于你的设备和运营商。
所以你能对它做点啥?批量。为了避免每两秒钟唤醒一次收音机, 请在接下来的几分钟内预取用户可能需要的内容。正确的批处理方式是高度动态的, 具体取决于你的应用程序, 但是如果可能, 则应在接下来的3-4分钟内下载用户可能需要的数据。人们还可以根据用户的互联网类型或收费状态来编辑批次参数。例如, 如果用户在充电时使用WiFi, 则可以预取比用户在电池电量低的移动互联网上更多的数据。考虑所有这些变量可能是一件艰难的事情, 只有很少的人会做。幸运的是, 有GCM Network Manager可以解救!
GCM Network Manager是一个非常有用的类, 具有许多可自定义的属性。你可以轻松地安排重复任务和一次性任务。在重复任务中, 你可以设置最低和最高的重复间隔。这样不仅可以批量处理你的请求, 还可以批量处理其他应用程序的请求。收音机每隔一段时间只能唤醒一次, 并且在收音机启动时, 队列中的所有应用程序都会下载并上传应有的内容。该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.反思
反射是类和对象检查自己的构造函数, 字段, 方法等的能力。通常用于向后兼容, 以检查给定的方法是否可用于特定的OS版本。如果你必须为此目的使用反射, 请确保缓存响应, 因为使用反射非常慢。一些广泛使用的库也使用反射, 例如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的内存, 而使用原始int占用了7MB以下的内存, 因此只有一半。
SparseIntArray只是可以帮助你避免自动装箱的很酷的收藏之一。像Map <Integer, Long>这样的地图可以用SparseLongArray替换, 因为地图的值是Long类型。如果你查看SparseLongArray的源代码, 你会发现一些非常有趣的东西。在底层, 它基本上只是一对数组。你也可以类似地使用SparseBooleanArray。
如果你阅读源代码, 可能会注意到一条注释, 说SparseIntArray可能比HashMap慢。我已经做了很多实验, 但是对我来说, SparseIntArray在内存和性能方面总是更好。我想你仍然可以选择哪种方法, 尝试一下用例, 看看哪种最适合你。使用地图时, 绝对可以拥有SparseArrays。
7. OnDraw
如上所述, 优化性能时, 你可能会看到优化经常运行的代码的最大好处。运行很多的函数之一是onDraw()。它负责在屏幕上绘制视图可能并不奇怪。由于设备通常以60 fps的速度运行, 因此该功能每秒运行60次。每帧都有16毫秒的处理时间, 包括准备和绘制, 因此, 你应该避免使用慢速功能。屏幕上只有主线程可以绘制, 因此你应该避免对其执行昂贵的操作。如果将主线程冻结几秒钟, 则可能会显示臭名昭著的Application Not Responding(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, 它可以让你找出位图的分辨率。假设你的位图为1024×768, 用于显示它的ImageView仅为400×300。你应该将位图的分辨率除以2, 直到它仍然大于给定的ImageView。如果这样做, 它将对位图进行2倍的下采样, 从而得到512×384的位图。降采样的位图使用的内存减少了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();
}
如果要检测严格模式可以发现的每个问题, 也可以使用detectAll()。与许多性能提示一样, 你不应盲目尝试修复所有”严格模式”报告。只需对其进行调查, 如果你确定这不是问题, 请不要理会。另外, 请确保仅将严格模式用于调试, 并始终在生产版本中将其禁用。
调试性能:专业方法
现在, 让我们看一些工具, 这些工具可以帮助你发现瓶颈, 或者至少表明出了问题。
1. Android显示器
这是Android Studio中内置的工具。默认情况下, 你可以在左下角找到Android Monitor, 并可以在其中的2个标签之间切换。 Logcat和监视器。 “监视器”部分包含4个不同的图形。网络, CPU, GPU和内存。它们是很容易解释的, 因此我将快速浏览它们。这是在下载某些JSON时解析图表的屏幕截图。
网络部分以KB / s为单位显示传入和传出流量。 CPU部分以百分比显示CPU使用率。 GPU监视器显示渲染UI窗口所需的时间。这是这4种显示器中最详细的显示器, 因此, 如果你需要有关它的更多详细信息, 请阅读此文章。
最后, 我们有”内存”监视器, 你可能会使用最多。默认情况下, 它显示当前的可用和已分配内存量。你也可以强制使用垃圾回收, 以测试使用的内存量是否下降。它具有一个称为Dump Java Heap的有用功能, 它将创建一个HPROF文件, 可以使用HPROF Viewer和Analyzer打开该文件。这样一来, 你便可以查看已分配了多少个对象, 什么占用了多少内存, 以及可能导致内存泄漏的对象。学习如何使用该分析仪并不是目前最简单的任务, 但这是值得的。你可以使用Memory Monitor进行的下一件事是进行一些定时的分配跟踪, 你可以根据需要启动和停止它。在许多情况下, 例如在滚动或旋转设备时, 它可能很有用。
2. GPU透支
这是一个简单的帮助程序工具, 你可以在启用开发人员模式后在”开发人员选项”中激活它。选择”调试GPU绘制”, “显示绘制区域”, 屏幕将显示一些奇怪的颜色。没关系, 那是应该发生的事情。颜色表示特定区域的透支次数。本色意味着没有透支, 这就是你的目标。蓝色表示透支1, 绿色表示透支2, 粉红色透支3, 红色透支4。
虽然看到真实的颜色是最好的, 但是你总是会看到一些透支, 尤其是在文本, 导航抽屉, 对话框等周围。因此, 请勿尝试完全摆脱它。如果你的应用是蓝色或绿色, 则可能很好。但是, 如果在一些简单的屏幕上看到太多红色, 则应调查发生了什么。如果你继续添加而不是替换它们, 则可能是太多的碎片堆积在一起。正如我上面提到的, 绘图是应用程序中最慢的部分, 因此, 如果要绘制的图层超过三层, 则毫无意义。随时使用它查看你喜欢的应用程序。你将看到, 即使下载量超过十亿的应用程序也会出现红色区域, 因此在尝试优化时, 请放轻松一点。
3. GPU渲染
这是开发人员选项中的另一个工具, 称为Profile GPU渲染。选择后, 选择”屏幕上显示为条形”。你会注意到屏幕上出现了一些彩色的条。由于每个应用程序都有单独的栏, 所以奇怪的是状态栏有自己的栏, 并且如果你有软件导航按钮, 它们也有自己的栏。无论如何, 当你与屏幕互动时, 这些栏会更新。
条形图由3-4种颜色组成, 根据Android文档, 它们的大小确实很重要。越小越好。底部有蓝色, 代表创建和更新视图的显示列表所用的时间。如果这部分太高, 则意味着有很多自定义视图工程图, 或者在onDraw()函数中完成了许多工作。如果你使用的是Android 4.0+, 则会在蓝色的上方看到一个紫色的条。这表示将资源转移到渲染线程所花费的时间。接下来是红色部分, 它表示Android的2D渲染器向OpenGL发布命令以绘制和重新绘制显示列表所花费的时间。顶部的橙色栏代表CPU等待GPU完成工作的时间。如果太高, 则表明该应用在GPU上的工作量过多。
如果你足够好, 则橙色上方还有一种颜色。这是一条绿线, 代表16 ms的阈值。由于你的目标应该以60 fps的速度运行你的应用程序, 因此每帧绘制时间为16毫秒。如果你不这样做, 则可能会跳过某些帧, 应用程序可能会变得生涩, 用户肯定会注意到。请特别注意动画和滚动, 这是平滑度最重要的地方。即使你可以使用此工具检测到一些跳帧, 它也无法真正帮助你确定问题出在哪里。
4.层次结构查看器
这是我最喜欢的工具之一, 因为它功能强大。你可以通过”工具”->” Android”->” Android设备监视器”从Android Studio启动它, 也可以将其作为”监视器”放在sdk / tools文件夹中。你也可以在其中找到一个独立的hierarachyviewer可执行文件, 但由于已过时, 你应该打开监视器。但是, 你打开Android设备监视器时, 请切换到”层次结构查看器”透视图。如果你没有看到分配给你设备的任何正在运行的应用, 则可以执行以下几项操作来修复它。也可以尝试查看此问题线程, 那里的人们遇到各种各样的问题和各种各样的解决方案。某些东西也应该为你工作。
借助Hierarchy Viewer, 你可以(显然)获得有关视图层次结构的整洁概览。如果你在单独的XML中看到每个布局, 则可能会轻易发现无用的视图。但是, 如果继续合并布局, 则很容易造成混淆。这样的工具可以很容易地发现, 例如一些RelativeLayout, 它只有一个孩子, 另一个RelativeLayout。这使得其中之一可移动。
避免调用requestLayout(), 因为它会导致遍历整个视图层次结构, 以找出每个视图应该有多大。如果与测量存在冲突, 则可能会遍历层次结构多次, 如果在某些动画过程中发生这种情况, 则肯定会跳过某些帧。如果你想了解有关Android如何绘制其视图的更多信息, 可以阅读此内容。让我们看一个在”层次结构查看器”中看到的视图。
右上角包含一个按钮, 用于最大化独立窗口中特定视图的预览。在其下, 你还可以在应用程序中查看视图的实际预览。下一个项目是一个数字, 代表给定视图有多少个子代, 包括视图本身。如果选择一个节点(最好是根节点)并按”获取布局时间”(3个彩色圆圈), 则将再填充3个值, 并且彩色圆圈显示为标注的度量, 布局和绘制。测量阶段代表测量给定视图所花费的时间可能并不令人震惊。布局阶段与渲染时间有关, 而绘图是实际的绘图操作。这些值和颜色是相对的。绿色表示该视图呈现在树中所有视图的前50%中。黄色表示在树中所有视图的慢50%中进行渲染, 红色表示给定视图是最慢的视图之一。由于这些值是相对的, 因此始终会有红色。你根本无法避免它们。
在这些值下, 你具有类名称, 例如” TextView”, 对象的内部视图ID和在XML文件中设置的视图的android:id。我敦促你养成在所有视图中添加ID的习惯, 即使你没有在代码中引用它们也是如此。它将使在Hierarchy Viewer中识别视图非常简单, 并且如果你在项目中进行了自动化测试, 还将使定位元素的速度大大加快。这样可以节省你和你的同事编写它们的时间。将ID添加到XML文件中添加的元素非常简单。但是动态添加的元素呢?好吧, 事实证明也很简单。只需在values文件夹中创建一个ids.xml文件, 然后键入必填字段。它看起来可能像这样:
<resources>
<item name="item_title" type="id"/>
<item name="item_body" type="id"/>
</resources>
然后在代码中, 可以使用setId(R.id.item_title)。它再简单不过了。
优化UI时, 还有两件事要注意。通常, 你应该避免使用深层次结构, 而应选择浅层次(也许是宽层次)。不要使用不需要的布局。例如, 你可能可以用RelativeLayout或TableLayout替换一组嵌套的LinearLayouts。你可以随意尝试不同的布局, 而不必总是使用LinearLayout和RelativeLayout。另外, 在需要时尝试创建一些自定义视图, 如果做得好, 可以显着提高性能。例如, 你知道Instagram不使用TextViews显示评论吗?
你可以在Android Developers网站上找到有关Hierarchy Viewer的更多信息, 其中包含有关不同窗格的描述, 使用Pixel Perfect工具等。我还要指出的另一件事是, 将视图捕获到.psd文件中, 可以通过”捕获窗口层”按钮。每个视图都位于单独的图层中, 因此在Photoshop或GIMP中隐藏或更改它非常简单。哦, 这是向每个视图添加ID的另一个原因。这将使图层具有实际上有意义的名称。
你会在开发人员选项中找到更多调试工具, 因此我建议你激活它们并查看它们的作用。可能出什么问题了?
Android开发人员网站包含一组最佳的性能最佳实践。它们涉及很多不同的领域, 包括内存管理, 而我还没有真正谈论过。我默默地忽略了它, 因为处理内存和跟踪内存泄漏是一个完全独立的故事。使用第三方库有效显示图像会有所帮助, 但是如果你仍然遇到内存问题, 请查看Square制造的Leak canary, 或阅读此书。
本文总结
所以, 这是个好消息。坏消息是, 优化Android应用程序要复杂得多。有很多方法可以完成所有工作, 因此你应该熟悉它们的优缺点。通常, 没有任何仅能解决问题的灵丹妙药。只有了解幕后发生的事情, 你才能选择最适合你的解决方案。仅仅因为你最喜欢的开发人员说某事是好事, 并不一定意味着它是你的最佳解决方案。还有很多地方需要讨论, 并且有更多高级的概要分析工具, 因此我们下次可能会涉及到它们。
确保你向顶尖的开发商和顶尖的公司学习。你可以在此链接上找到数百个工程博客。显然, 这不仅是与Android相关的内容, 因此, 如果你仅对Android感兴趣, 则必须过滤特定博客。我强烈推荐Facebook和Instagram的博客。尽管Android上的Instagram UI值得怀疑, 但他们的工程博客中仍有一些非常酷的文章。对我来说, 真是太好了, 它很容易看到每天处理数亿用户的公司的工作情况, 因此不阅读他们的博客似乎很疯狂。世界瞬息万变, 因此, 如果你不不断尝试改进, 向他人学习并使用新工具, 那么你将被抛在后面。正如马克·吐温(Mark Twain)所说, 不读书的人比不读书的人没有优势。