作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Tibor Kaputa的头像

Tibor Kaputa

Tibor是一名熟练的开发人员,拥有超过8年的Java经验, Kotlin, JavaScript, and C++. 他也是一名资深的服务器管理员.

Previously At

T Systems
Share

Android设备有很多核心,所以编写流畅的应用程序对任何人来说都是一项简单的任务? Wrong. 因为Android上的一切都可以通过许多不同的方式完成, 选择最好的选择可能很难. 如果你想选择最有效的方法,你必须知道幕后发生了什么. Luckily, 你不必依靠你的感觉或嗅觉, 因为有很多工具可以通过测量和描述正在发生的事情来帮助您找到瓶颈. 适当优化和流畅的应用程序大大提高了用户体验,也减少了电池消耗.

让我们先看一些数字,看看优化到底有多重要. According to a Nimbledroid帖子, 86%的用户(包括我)因为性能不佳而只使用了一次就卸载了应用. 如果你正在加载一些内容,你只有不到11秒的时间向用户展示. 只有三分之一的用户会给你更多的时间. 你也可能因此在Google Play上获得许多差评.

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

测试用户的耐心是卸载的捷径.

每个用户都会反复注意到的第一件事就是应用的启动时间. According to Nimbledroid的另一个帖子在美国,排名前100的应用中,有40个在2秒内启动,70个在3秒内启动. So if possible, 一般来说,您应该尽快显示一些内容,并稍微延迟背景调查和更新.

永远记住,过早优化是万恶之源. 你也不应该在微优化上浪费太多时间. 您将看到优化经常运行的代码的最大好处. 例如,这包括 onDraw() 函数,每帧运行,理想情况下每秒运行60次. 绘图是最慢的操作,所以尝试只重画你必须重画的东西. 稍后将详细介绍这一点.

性能技巧

Enough theory, 如果性能对你很重要,这里列出了一些你应该考虑的事情.

1. String vs. StringBuilder

假设你有一个字符串, 出于某种原因,你想给它追加更多的字符串1万次. 代码可能是这样的.

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

你可以在Android Studio监视器上看到一些字符串连接是多么低效. 有大量的垃圾收集(GC)正在进行.

String vs StringBuilder

在我的安卓5系统的设备上,这个操作大约需要8秒.1.1. 实现相同目标的更有效的方法是使用StringBuilder,如下所示.

StringBuilder sb = new StringBuilder("hello");
for (int i = 0; i < 10000; i++) {
    sb.追加(“世界”);
}
字符串= sb.toString();

在同样的设备上,这几乎是瞬间发生的,不到5毫秒. CPU和内存的可视化几乎是完全平坦的, 所以你可以想象这个进步有多大. Notice though, 这是为了实现这种差异, 我们必须添加1万个字符串, 你可能不经常这样做. 因此,如果您一次只添加几个字符串,您将看不到任何改进. 顺便说一下,如果你这样做了:

String String = "hello" + " world";

它在内部被转换为StringBuilder,所以它会工作得很好.

您可能想知道,为什么第一种连接字符串的方式如此之慢? 这是因为字符串是不可变的, 一旦它们被创造出来, 它们无法改变. 即使您认为您正在更改字符串的值, 你实际上是在用新的值创建一个新的字符串. 在这样的例子中:

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

你在内存中得到的不是1个字符串" hello world ",而是2个字符串. 字符串myString将包含" hello world ",如您所料. However, 值为" hello "的原始字符串仍然存在, 没有任何参考, 等待垃圾回收. 这也是为什么应该将密码存储在字符数组中而不是字符串中的原因. 如果您将密码存储为字符串, 它将以人类可读的格式保留在内存中,直到下一次GC,时间长度不可预测. 回到上面描述的不变性, 字符串将留在内存中,即使您在使用它后为它赋另一个值. 但是,如果您在使用密码后清空char数组,那么它将从任何地方消失.

2. 选择正确的数据类型

在开始编写代码之前,应该决定将为集合使用什么数据类型. 例如,你是否应该使用 Vector or an ArrayList? 这取决于你的用途. 如果需要线程安全的集合, 哪一个线程一次只允许一个线程使用它, 你应该选一个 Vector,因为它是同步的. 在其他情况下,你应该坚持使用 ArrayList除非你真的有特殊的理由使用向量.

如果想要一个具有唯一对象的集合呢? 你应该选a Set. 它们不能包含重复的设计,所以您不必自己处理它. 有多种类型的集合,所以选择一个适合您的用例. 对于一组简单的唯一项,您可以使用 HashSet. 如果希望保留插入项的顺序,请选择a LinkedHashSet. A TreeSet 自动对项目进行排序,因此您不必对其调用任何排序方法. 它还应该有效地对项目进行分类,而不必考虑 排序算法.

Data dominates. 如果您选择了正确的数据结构并且组织得很好, 算法几乎总是不言自明的. 编程的核心是数据结构,而不是算法.
——Rob Pike的5条编程规则

对整数或字符串排序非常简单. 但是,如果您想按某些属性对类进行排序该怎么办呢? 假设你正在写一份你所吃的食物的清单,并存储它们的名字和时间戳. 你如何按照时间戳从低到高对这些饭菜进行排序? 幸运的是,这很简单. 这足以实现 Comparable interface in the Meal 类并重写 compareTo() function. 要按时间戳从最低到最高对膳食进行排序,我们可以这样写.

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

3. 位置更新

有很多应用收集用户的位置信息. 为此,您应该使用Google定位服务API, 哪个包含很多有用的功能. There is a 单独的文章 所以我就不再重复了.

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

首先,根据需要只使用最精确的位置. 例如,如果你正在做一些天气预报,你不需要最准确的位置. 基于网络获取一个非常粗糙的区域更快,而且更省电. 你可以通过设置优先级来实现它 LocationRequest.PRIORITY_LOW_POWER.

你也可以用的函数 LocationRequest called setSmallestDisplacement (). 设置这个在米将导致你的应用程序不会被通知的位置变化,如果它小于给定的值. For example, 如果你有一张附近餐馆的地图, 把最小位移设为20米, 如果用户只是在房间里走来走去,该应用程序将不会请求查看餐厅. 这些要求是没有用的,因为附近也不会有新的餐馆.

第二条规则是只在你需要的时候请求位置更新. 这是不言自明的. 如果你真的在构建天气预报应用, 您不需要每隔几秒钟请求一次位置, 因为你可能没有这样精确的预测(如果你有,请联系我). You can use the setInterval() 功能用于设置设备将更新你的应用程序有关位置的所需间隔. 如果多个应用不断请求用户的位置, 每个应用程序都会在每次新位置更新时收到通知, 即使你有一个更高的 setInterval() set. 以防止你的应用程序被告知太频繁, 确保始终设置最快更新间隔 setFastestInterval ().

最后,第三条规则是,只有在需要时才请求位置更新. 如果你每隔x秒在地图上显示一些附近的物体,应用程序就会进入后台, 你不需要知道新的位置. 如果用户无法看到地图,就没有理由更新地图. 确保在适当的时候停止侦听位置更新,最好是在 onPause(). 然后,您可以在 onResume().

4. Network Requests

您的应用程序很有可能使用互联网下载或上传数据. 如果是,你有几个理由要注意 处理网络请求. 其中之一是移动数据,这对很多人来说是非常有限的,你不应该浪费它.

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

Network Requests

那么,你能做些什么呢? Batch. 为了避免每隔几秒就吵醒一次无线电, 预取用户在接下来的几分钟内可能需要的东西. 正确的批处理方式是高度动态的,这取决于你的应用, 但如果可能的话, 您应该在接下来的3-4分钟内下载用户可能需要的数据. 还可以根据用户的互联网类型或充电状态编辑批处理参数. For example, 如果用户在WiFi上充电, 与用户使用低电量的移动互联网相比,您可以预取更多的数据. 考虑所有这些变量可能是一件困难的事情,只有少数人会这样做. 幸运的是,有 GCM 网络管理器的救援!

GCM Network Manager是一个非常有用的类,具有许多可定制的属性. 你可以很容易地安排重复的和一次性的任务. 在重复任务时,您可以设置最低和最高重复间隔. 这不仅允许批处理你的请求,还允许批处理来自其他应用程序的请求. 收音机每隔一段时间只需要唤醒一次, 当它升起的时候, 队列中的所有应用程序都下载并上传它们应该下载的内容. 该管理器还了解设备的网络类型和充电状态, 所以你可以做出相应的调整. 你可以找到更多的细节和样本 this article,我强烈建议你去看看. 一个示例任务是这样的:

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

顺便说一下,从安卓3开始.0,如果你在主线程上执行网络请求,你将得到 NetworkOnMainThreadException. 这肯定会警告你不要再这样做了.

5. Reflection

反射是类和对象检查它们自己的构造函数的能力, fields, methods, and so on. 它通常用于向后兼容, 来检查给定方法是否适用于特定的操作系统版本. 如果你必须使用反射来达到这个目的, 确保缓存响应, 因为使用反射很慢. 一些广泛使用的库也使用了反射,比如Roboguice的依赖注入. 这就是为什么你应该选择匕首2. 有关反射的更多详细信息,请查看 separate post.

6. Autoboxing

自动装箱和拆箱是将基本类型转换为对象类型的过程, or vice versa. 在实践中,它意味着将整型转换为整型. 为了实现这一点,编译器使用 Integer.valueOf() 函数内部. 转换不仅缓慢,对象也比它们的原始对等物占用更多的内存. 让我们看一些代码.

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

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

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

这个解决方案大约运行2ms,快了25倍. 如果你不相信我,试试吧. 每个设备的数字显然会有所不同,但它仍然会快得多. 这也是一个非常简单的优化步骤.

你可能不会像这样创建一个Integer类型的变量. 但是当它很难避免的时候呢? 比如在地图中,你需要使用对象 Map? 看看很多人使用的解决方案.

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

在映射中插入100k随机整数需要运行大约250ms. 现在看看SparseIntArray的解决方案.

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

这要少得多,大约50ms. 这也是提高性能最简单的方法之一, 因为没有什么复杂的事情要做, 代码也保持可读性. 使用第一种解决方案运行一个清晰的应用程序占用了我13MB的内存, 使用原始整型需要7MB, 所以只是它的一半.

SparseIntArray只是一个很酷的集合,它可以帮助您避免自动装箱. A map like Map 可以被 SparseLongArray,因为映射的值为类型 Long. 如果你看一下 source code of SparseLongArray,你会看到一些非常有趣的东西. 在底层,它基本上只是一对数组. 你也可以用a SparseBooleanArray similarly.

如果您阅读了源代码,您可能会注意到这样的注释 SparseIntArray 可以比 HashMap. 我做了很多实验,但对我来说 SparseIntArray 在内存和性能方面总是更好吗. 我想这还是取决于你的选择, 用您的用例进行试验,看看哪个最适合您. 当然有 SparseArrays 当你在使用地图的时候.

7. OnDraw

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

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

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

这是你应该避免的事情之一 onDraw() 是分配对象像油漆. 在构造函数中准备好一切,这样绘图时就准备好了. 即使你有 onDraw() 优化后,您应该只在必要时调用它. 还有什么比调用一个优化过的函数更好的呢? 不调用任何函数. 如果您想绘制文本,有一个非常简洁的辅助函数叫做 drawText(),您可以在其中指定文本、坐标和文本颜色等内容.

8. ViewHolders

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

静态类ViewHolder {
    TextView title;
    TextView text;

    公共ViewHolder(视图视图){
        title = (TextView)视图.findViewById(R.id.title);
        text = (TextView)视图.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”);

您可以在互联网上找到许多关于此模式的有用信息. 当列表视图中有多个不同类型的元素时,也可以使用它, 比如一些section头.

9. Resizing Images

很有可能,你的应用程序将包含一些图像. 如果你从网上下载一些jpg,它们的分辨率可能非常高. 然而,它们将被显示在更小的设备上. 即使你用手机的摄像头拍照, 由于照片的分辨率比显示器的分辨率大得多,因此在显示之前需要缩小尺寸. 在显示图像之前调整它们的大小是至关重要的. 如果你尝试以全分辨率显示它们,你很快就会耗尽内存. There 是不是有很多关于在Android文档中有效显示位图的文章, 我试着总结一下.

你有一个位图,但你对它一无所知. 在你的服务中有一个有用的位图标志,叫做inJustDecodeBounds, 它能让你找到位图的分辨率. 让我们假设您的位图是1024x768,而用于显示它的ImageView只有400x300. 你应该继续将位图的分辨率除以2,直到它仍然大于给定的ImageView. 如果您这样做,它将对位图进行2倍的采样,从而得到512x384的位图. 下采样位图使用的内存减少了4倍, 这将帮助您避免著名的OutOfMemory错误.

既然你知道怎么做,你就不应该这样做. 至少,如果你的应用严重依赖于图片,而且它不只是1-2张图片. 一定要避免手动调整大小和回收图像, 使用一些第三方库. 最受欢迎的是 Picasso by Square, 通用图像加载器, Fresco 或者我最喜欢的, Glide. 围绕它有一个庞大的活跃开发者社区, 所以你也可以在GitHub的问题部分找到很多有帮助的人.

10. Strict Mode

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

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

如果您想检测严格模式可以找到的每个问题,您也可以使用 detectAll(). 与许多性能提示一样,您不应该盲目地尝试修复严格模式报告的所有内容. 调查一下,如果你确定这不是问题,那就别管它了. 还要确保只在调试时使用严格模式, 并且在生产构建中始终禁用它.

调试性能:Pro方式

现在让我们来看一些可以帮助您找到瓶颈的工具, 或者至少表明出了什么问题.

1. Android Monitor

这是一个内置在Android Studio中的工具. By default, 你可以在左下角找到Android Monitor, 你可以在两个选项卡之间切换. Logcat和监视器. 监视器部分包含4个不同的图. 网络、CPU、GPU、内存. 它们是不言自明的,所以我将快速地过一遍. 下面是在解析下载的JSON时所拍摄的图表截图.

Android Monitor

网络部分以KB/s为单位显示传入和传出的流量. CPU部分以百分比显示CPU的使用情况. GPU监视器显示渲染UI窗口的帧所花费的时间. 这是这4个显示器中最详细的,所以如果你想了解更多细节, read this.

最后是 Memory monitor,你可能会用得最多. 默认情况下,它显示当前空闲内存和已分配内存的数量. 您也可以强制使用它进行垃圾收集,以测试使用的内存量是否下降. 它有一个有用的特性叫做Dump Java Heap, 这将创建一个HPROF文件,可以用 HPROF查看器和分析器. 这将使您能够看到分配了多少对象, 什么占用了多少内存, 也许是哪些对象导致了内存泄漏. 学习如何使用这个分析器并不是最简单的任务,但它是值得的. 你可以用内存监视器做的下一件事是做一些定时分配跟踪, 你可以随心所欲地开始和停止. 它在很多情况下都很有用,比如在滚动或旋转设备时.

2. GPU Overdraw

这是一个简单的辅助工具, 启用开发人员模式后,您可以在开发人员选项中激活该选项. 选择调试GPU透支,“显示透支区域”,你的屏幕会得到一些奇怪的颜色. 没关系,这是应该发生的事. 颜色表示一个特定区域被透支了多少次. 真正的颜色意味着没有透支,这是你应该追求的. 蓝色表示透支一次,绿色表示透支两次,粉色表示透支三次,红色表示透支四次.

GPU Overdraw

而看到真实的颜色是最好的, 你总是会看到一些透支, 尤其是关于文本, 导航的抽屉, 对话和更多. 所以不要试图完全摆脱它. 如果你的应用是蓝色或绿色的,那可能没问题. 然而,如果您在一些简单的屏幕上看到太多的红色,您应该调查一下发生了什么. 如果您不断添加而不是替换它们,可能会有太多的片段堆叠在一起. 正如我上面提到的, 绘图是应用程序中最慢的部分, 所以如果要在上面画3层以上就没有意义了. 请随意检查您最喜欢的应用程序与它. 你会发现,即使下载量超过10亿的应用也有红色区域, 所以当你尝试优化的时候,不要着急.

3. GPU Rendering

这是开发人员选项中的另一个工具,称为 配置文件GPU渲染. 选中后,选择“On screen as bars”. 您将注意到屏幕上出现一些彩色条. 因为每个应用程序都有单独的条, 奇怪的是,状态栏有自己的图标, 如果你有软件导航按钮, 他们也有自己的酒吧. 无论如何,当你与屏幕交互时,这些栏会更新.

GPU Rendering

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

如果你够好,在橙色上面还有一种颜色. 绿线表示16毫秒阈值. 因为你的目标应该是以60 fps的速度运行你的应用,所以你有16毫秒的时间来绘制每一帧. 如果你做不到, 有些帧可能会被跳过, 这款应用可能会变得不稳定, 用户肯定会注意到. 特别注意动画和滚动,这是平滑最重要的地方. 尽管您可以使用此工具检测一些跳过的帧, 它并不能真正帮助你找出问题到底在哪里.

4. 层次观众

这是我最喜欢的工具之一,因为它真的很强大. You can start it from Android Studio through Tools -> Android -> Android Device Monitor, 或者它也在你的sdk/tools文件夹中作为“monitor”. 您还可以在这里找到一个独立的hierarchachyviewer可执行文件, 但由于它已被弃用,您应该打开监视器. 无论您打开Android Device Monitor,还是切换到层次观众透视图. 如果你没有看到任何正在运行的应用分配给你的设备, there 你能做些什么来解决这个问题吗. 也可以试着查看 this 问题线程,有各种各样的问题和各种各样的解决方案的人. 你也应该有办法.

使用层次观众,你可以很清楚地看到你的视图层次结构。. 如果您在单独的XML中看到每个布局,您可能很容易发现无用的视图. 然而,如果你继续组合布局,它很容易变得混乱. 像这样的工具很容易被发现, for example, 一些使用, 哪个只有一个孩子, 另一个使用. 这使得其中一个可以移动.

Avoid calling requestLayout(),因为它导致遍历整个视图层次结构,以找出每个视图应该有多大. 如果与测量值有冲突, 层次结构可能被遍历多次, 哪个在动画中发生, 它肯定会使一些帧被跳过. 如果你想了解更多关于Android如何吸引浏览量的信息,你可以这么做 read this. 让我们看一下层次观众中的一个视图.

层次观众

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

在这些值下面是类名, 例如" TextView ", 对象的内部视图ID, 还有视图的android:id, 是在XML文件中设置的吗. 我强烈建议您养成向所有视图添加id的习惯,即使您没有在代码中引用它们. 它将使在层次观众中识别视图变得非常简单, 如果您在项目中使用了自动化测试, 这也将使定位元素的速度大大加快. 这将为你和你的同事节省一些时间. 向XML文件中添加的元素添加id非常简单. 但是动态添加的元素呢? 其实也很简单. 只需创建一个id.在values文件夹内的XML文件,并在所需字段中键入. 它可以是这样的:


    
    

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

在优化UI时,还有一些事情需要注意. 一般来说,你应该避免深层的层次结构,而更喜欢浅层的,也许是宽泛的层次结构. 不要使用你不需要的布局. 例如,您可以替换一组嵌套的 LinearLayouts with either a RelativeLayout, or a TableLayout. 随意尝试不同的布局,不要总是使用 LinearLayout and RelativeLayout. 还可以尝试在需要时创建一些自定义视图, 如果操作得当,可以显著提高性能. 例如,你知道Instagram不使用TextViews 显示评论?

你可以找到更多关于层级查看器的信息 Android开发者网站 对不同的窗格进行描述,使用像素完美工具等. 我要指出的另一件事是在a中捕捉视图 .psd文件,这可以通过“捕获窗口图层”按钮来完成. 每个视图都在一个单独的图层中, 所以在Photoshop或GIMP中隐藏或更改它非常简单. 这是给每个视图添加ID的另一个原因. 它会让这些层有真正有意义的名字.

您可以在Developer选项中找到更多调试工具, 所以我建议你激活它们,看看它们在做什么. 有什么可能出错呢?

Android开发者网站包含一组 性能最佳实践. 它们涵盖了很多不同的领域, 包括内存管理, 我还没说过呢. 我默默地忽略了它,因为处理内存和跟踪内存泄漏是完全不同的事情. 使用第三方库来有效地显示图像会有很大帮助, 但如果你还有记忆问题, check out Leak canary 由Square制作,或read this.

Wrapping Up

这就是好消息. 坏消息是,优化 Android 应用程序要复杂得多. 做任何事情都有很多种方法, 所以你应该熟悉它们的利弊. 通常没有什么只有好处的灵丹妙药. 只有了解了幕后发生的事情,你才能选择最适合自己的解决方案. 仅仅因为你最喜欢的开发者说某些东西很好, 这并不一定意味着它对你来说是最好的解决方案. 有更多的领域需要讨论,也有更多更高级的分析工具, 我们下次可能会讲到.

确保你向顶级开发者和顶级公司学习. 你可以在这里找到几百个工程博客 this link. 显然,这不仅仅是Android相关的东西, 所以如果你只对Android感兴趣, 你必须过滤特定的博客. 我强烈推荐的博客 Facebook and Instagram. 尽管Android上的Instagram用户界面是有问题的, 他们的工程博客上有一些很酷的文章. 对我来说,在每天处理数亿用户的公司中,很容易看到事情是如何完成的,这真是太棒了, 所以不读他们的博客看起来很疯狂. 世界变化很快, 所以如果你不经常努力提高, 向他人学习并使用新工具, 你会被抛在后面. 正如马克·吐温所说,一个不读书的人并不比一个不会读书的人有什么优势.

聘请Toptal这方面的专家.
Hire Now
Tibor Kaputa的头像
Tibor Kaputa

Located in Jasov, Košice斯洛伐克地区

Member since January 8, 2016

作者简介

Tibor是一名熟练的开发人员,拥有超过8年的Java经验, Kotlin, JavaScript, and C++. 他也是一名资深的服务器管理员.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previously At

T Systems

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal开发者

Join the Toptal® community.