[Turn] for optimizing Android applications Tips and Tools

Android devices have a lot of kernel, so smooth writing applications for anyone is a simple task, right? error. Since all content on Android can be done in many ways, choosing the best option may be very difficult. If you want to choose the most effective way, you must know the bottom of what happened. Fortunately, you do not have to rely on their own feelings or sense of smell, because there are many tools that can measure and describe what is happening to help you find the bottlenecks. Proper optimization and smooth application greatly improves the user experience, while also reducing power consumption.

Let's look at some numbers to consider the importance of optimization. According to Nimbledroid posts , 86% of users (including me) after using them because of poor performance but only once uninstalled. If you want to load something, it takes less than 11 seconds to display to the user. Only one in three users can give you more time. For this reason, you may also receive a lot of bad reviews on Google Play.

Build better applications: Android performance mode

Test user's patience is to uninstall shortcut.

 

Each user noticed over and over again the first thing is to start time of the application. According to another article Nimbledroid is in the top 100 applications, 40 within 2 seconds starts, 70 starts in 3 seconds. Therefore, if possible, you should show some delay content and background check and update as soon as possible.

Always remember that premature optimization is the root of all evil. You should not waste too much time micro-optimization. You will see the optimized code often runs the greatest benefit. For example, this includes onDraw()the functions of each frame of operations, ideally run 60 times per second. Drawing is the slowest operation, so try to redraw only what you need. There will be more on this later.

Performance Tips

Enough theory, here are some of you think that performance is important to you to do.

1. String vs StringBuilder

Suppose you have a string, and for some reason you want to attach more strings to it thousands of times. Code might look like this.

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

You can see some of the inefficiencies String connection on Android Studio Monitors. There are a lot of garbage collection (GC) is in progress.

String vs StringBuilder

It takes about 8 seconds to run on my pretty good device that has Android 5.1.1. A more efficient way to achieve the same goal is to use StringBuilder, like this.

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

On the same device, this situation occurs almost immediately, less than 5 milliseconds. CPU and Memory Visualizer is almost completely flat, so you can imagine how much this improvement. However, please note that in order to achieve this difference, we have an additional 10,000 strings, you may not do it often. So, if you only add a few strings, you will not see any improvement. By the way, if you do this:

String string = "hello" + " world";

It is internally converted StringBuilder, so it can work properly.

You may wonder why the first way Strings connection so slow? This is because the strings are immutable, so that once created they can not be changed. Even if you think you are changing the value of String, you actually create a new String with the new values. In one example:

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

You will get the string in memory is not a "hello world", but is actually two strings. As you might expect, String myString containing "hello world". However, the original String has a value of "hello" is still active, there is no reference, waiting for garbage collection. It is also you should password is stored in an array of char and not the cause of String. If the password is stored as a string, it will remain in a human readable format in memory until the next GC for a period of time unpredictable. Back invariance described above, even after you use it to assign another value to it, it will be retained in memory. However, if you empty char array after using a password, it will disappear from anywhere.

2. Select the correct data type

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

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

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

对整数或字符串进行排序非常简单。但是,如果您想通过某些属性对类进行排序,该怎么办?假设你正在写一份你吃的饭菜清单,并存储他们的名字和时间戳。您如何按时间戳从最低到最高排序餐?幸运的是,它非常简单。它足以ComparableMeal类中实现接口并覆盖该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位置服务API来实现此目的,其中包含许多有用的功能。有一篇关于使用它的文章,所以我不再重复了。

我想从绩效角度强调一些重要的观点。

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

您还可以使用LocationRequest被调用的函数setSmallestDisplacement()。以米为单位设置此选项将导致您的应用程序在位置更改小于给定值时不会收到通知。例如,如果您有一张附近餐馆的地图,并且您将最小位移设置为20米,那么如果用户只是在一个房间里走动,该应用程序将不会要求检查餐馆。请求没用,因为附近没有新的餐厅。

第二条规则是仅在您需要时经常请求位置更新。这是非常自我解释的。如果您真的正在构建该天气预报应用程序,则无需每隔几秒钟请求该位置,因为您可能没有这样精确的预测(如果您这样做,请与我联系)。您可以使用此setInterval()功能设置设备更新应用程序所需的时间间隔。如果多个应用程序不断请求用户的位置,即使您有更高的setInterval()设置,每个应用程序也会在每次新位置更新时得到通知。为防止过于频繁地通知您的应用,请务必始终设置最快的更新间隔setFastestInterval()

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

4.网络请求

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

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

Network requests

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

GCM网络管理器是一个非常有用的类,具有许多可自定义的属性。您可以轻松安排重复和一次性任务。在重复任务时,您可以设置最低重复间隔以及最高重复间隔。这样不仅可以批量处理您的请求,还可以批量处理来自其他应用程序的请求。收音机必须每隔一段时间才被唤醒一次,当它播出时,队列中的所有应用程序都会下载并上传它们应该播放的内容。此管理器还了解设备的网络类型和充电状态,因此您可以相应地进行调整。您可以在本文中找到更多详细信息和示例,我建议您查看。示例任务如下所示:

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版本。如果必须为此目的使用反射,请确保缓存响应,因为使用反射非常慢。一些广泛使用的库也使用Reflection,就像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;
}

该解决方案在大约2ms处运行,速度提高了25倍。如果你不相信我,请测试一下。每个设备的数字明显不同,但它应该仍然快得多。这也是一个非常简单的优化步骤。

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

Map<Integer, Integer> 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的内存,使用原始的int需要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文档中有效显示位图的文章,我会尝试总结它。

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

既然你知道怎么做,你就不应该这样做。...至少,如果您的应用程序严重依赖图像,并且它不仅仅是1-2张图像。绝对避免手动调整大小和回收图像等内容,使用一些第三方库。最受欢迎的是Picasso by Square,Universal Image LoaderFresco by Facebook,或者我最喜欢的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()。与许多性能提示一样,您不应盲目地尝试修复严格模式报告的所有内容。只是调查它,如果你确定这不是问题,请不要管它。另外,请确保仅将Strict Mode用于调试,并始终在生产版本上禁用它。

调试性能:Pro方式

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

1. Android监视器

这是Android Studio内置的工具。默认情况下,您可以在左下角找到Android监视器,您可以在那里切换2个选项卡。Logcat和监视器。监视器部分包含4个不同的图形。网络,CPU,GPU和内存。它们非常自我解释,所以我会很快通过它们。以下是在下载时解析某些JSON时所拍摄的图表的屏幕截图。

Android Monitor

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

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

2. GPU透支

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

GPU overdraw

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

3. GPU渲染

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

GPU rendering

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

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

4.层次结构查看器

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

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

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

Hierarchy Viewer

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

在您拥有类名的值下,例如“TextView”,对象的内部视图ID,以及您在XML文件中设置的视图的android:id。我建议你养成为所有视图添加ID的习惯,即使你没有在代码中引用它们。它将使层次结构查看器中的视图识别非常简单,如果您在项目中进行自动化测试,它还可以更快地定位元素。这将为您和您的同事写下它们节省一些时间。向XML文件中添加的元素添加ID非常简单。但是动态添加的元素呢?嗯,结果也很简单。只需在values文件夹中创建一个ids.xml文件,然后键入必填字段。它看起来像这样:

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

然后在代码中,您可以使用setId(R.id.item_title)。这简直太简单了。

优化UI时还有一些事项需要注意。一般情况下,你应该避免使用深层次结构,而应该选择浅层,可能是宽层。不要使用您不需要的布局。例如,您可以替换一组嵌套的LinearLayoutsRelativeLayout或a TableLayout。随意尝试不同的布局,不要总是使用LinearLayoutRelativeLayout。还可以尝试在需要时创建一些自定义视图,如果做得好,它可以显着提高性能。例如,您是否知道Instagram不使用TextViews来显示评论

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

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

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

包起来

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

Make sure that you learn from top developers and top companies. You can this link to find hundreds of projects blog. This is clearly not just Android related stuff, so if you are only interested in Android, you have to filter specific blog. I highly recommend Facebook and Instagram blog. Even Instagram UI on Android have problems with their project blog also has some very cool article. For me, it is easy to see how the company handles hundreds of millions of users every day to complete the work, so do not read their blog seem crazy. The world is changing very fast, so if you're not always working to improve, learn from others and use the new tool, you will be left behind. As Mark Twain said, do not read a man and a man can not read has no advantage.

 

Transfer: https://www.toptal.com/android/android-performance-tips-tools

Guess you like

Origin blog.csdn.net/mcs712/article/details/95312360