开始逐渐学习性能优化相关,这篇文章就是在阅读官方文档时做的笔记,好对性能优化有个大概的认识。
官方文档:
Best Practices for Performance
Performance Profiling Tools
Processes and Threads
扩展阅读:
Putting pixels on the screen involves four primary pieces of hardware. To greatly simplify, the CPU computes display lists, the GPU renders images to the display, the memory stores images and data, and the battery provides electrical power. Each of these pieces of hardware has constraints; pushing or exceeding those constraints causes your app to be slow, have bad display performance, or exhaust the battery.
像素要在设备上显示,主要需要四大硬件支持。简单的说,CPU负责计算工作,GPU负责渲染到界面上,内存存储图像和数据,电池提供电能进行运转。这些硬件都有其闵值,过度使用就会使你的App显得很卡。
Android Studio and your device provide profiling tools to record and visualize the rendering, compute, memory, and battery performance of your app. While profiling an app, you should disable Instant Run.
Android Studio和设备本身就提供了一些性能分析工具,使用这些工具时你应该关闭 Instant Run
。
有一下几大工具:
这些工具已经涉及到具体的分析性能优化,会另外学习,这里仅仅列出分析工具大类。
Visualize the rendering behavior and performance of your app.
分析GPU工作性能
Visualize the memory behavior and performance of your app.
分析内存
Visualize the CPU behavior and performance of your app.
分析CPU
Visualize the battery behavior and performance of your app.
分析电池
Sharing Memory:共享Java核心库内存
Allocating and Reclaiming App Memory: 每个应用独立的分配和回收内存
Restricting App Memory: 限制每个App占用的最大内存
Switching Apps: 管理进程时按照规则回收进程所占的内存空间
So the only way to completely release memory from your app is to release object references you may be holding, making the memory available to the garbage collector.
因为Android系统在一个app内分配内存时并不会替换原有的内存(而是 paging and memory-mapping),所以进行内存回收的唯一途径就是释放对该内存区域的引用,然后等待被垃圾回收器回收。
Each app process is forked from an existing process called Zygote. The Zygote process starts when the system boots and loads common framework code and resources (such as activity themes). To start a new app process, the system forks the Zygote process then loads and runs the app’s code in the new process. This allows most of the RAM pages allocated for framework code and resources to be shared across all app processes.
每个app启动时,系统会从 Zygote
进程上fork一个该app的进程,在该进程中加载和运行该app。这就保证了Java核心库所占用的内存可以被共享。所以每个app是一个独立的进程,占用独立的内存,使用独立的DVM。
这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另一方面是与Zygote进程共享了同一套Java核心库。这样不仅Android应用程序进程的创建过程很快,而且由于所有的Android应用程序进程都共享同一套Java核心库而节省了内存空间。
The Dalvik heap for each process is constrained to a single virtual memory range. This defines the logical heap size, which can grow as it needs to (but only up to a limit that the system defines for each app).
每个Dalvik有独立的堆内存,供app使用,但是系统会定义一个上限值。
To maintain a functional multi-tasking environment, Android sets a hard limit on the heap size for each app. The exact heap size limit varies between devices based on how much RAM the device has available overall. If your app has reached the heap capacity and tries to allocate more memory, it will receive an OutOfMemoryError.
Android系统为了维护一个多任务的环境,Android规定了每个app所占用的最大内存。如果app的内存占满了,并且还要求分配内存时,就会报出OOM异常。
As the system runs low on memory, it may kill processes in the LRU cache beginning with the process least recently used, but also giving some consideration toward which processes are most memory intensive.
当用户使用其他app时,即你的app在后台运行时,即该进程优先级是后台进程,在系统内存不足时,会根据进程的优先级和LRU算法清除进程,同时也会考虑内存占用这个因素。
When the system begins killing processes in the LRU cache, although it primarily works bottom-up, it does give some consideration to which processes are consuming more memory and will thus provide the system more memory gain if killed. So the less memory you consume while in the LRU list overall, the better your chances are to remain in the list and be able to quickly resume.
系统在回收进程释放资源时,虽然是按照LRU顺序来清理的,但确实会考虑进程占用内存这个因素,如果杀死一个进程就能提供足够的内存的话,那么就杀死该进程。所以你应该让你的进程占用最少的内存,来提高一直在后台留存的机会,这样在用户重新打开你的app时能够很快重现。
You should consider RAM constraints throughout all phases of development, including during app design (before you begin development). There are many ways you can design and write code that lead to more efficient results.
你应该在设计、编码该app时就考虑到内存限制这个因素。在设计程序和编码实现时应该尽量使你的app变得高效率。
Leaving a service running when it’s not needed is one of the worst memory-management mistakes an Android app can make. So don’t be greedy by keeping a service for your app running. Not only will it increase the risk of your app performing poorly due to RAM constraints, but users will discover such misbehaving apps and uninstall them.
小心使用Service。你应该尽量使用 IntentService
来实现一些需求。因为Service空转的话,你的app会冒着OOM的风险,并且一旦用户发现,极有可能会卸载该app。
Release memory when your user interface becomes hidden and as memory becomes tight.
当你的app变为后台进程时,或者在系统内存紧张时,你应该在 onTrimMemory
监听不同的状态来中释放资源,保证你的app占用最小的内存,这样提高在后台留存的几率。
Check how much memory you should use 获取当前可分配的内存
*you can request a larger heap size by setting the largeHeap
attribute to “true”*
使用 getMemoryClass()
方法获取当前设备可以给每个app分配的内存。也可以设置 largeHeap
属性获取更大的内存,此时可以通过getLargeMemoryClass()
方法获取。
Using the extra memory will increasingly be to the detriment of the overall user experience because garbage collection will take longer and system performance may be slower when task switching or performing other common operations.
但不建议使用该属性,这会降低用户体验,因为垃圾回收器会消耗更多的时候来回收垃圾,会造成卡顿现象。
Take advantage of optimized containers in the Android framework, SparseArray,SparseBooleanArray, and LongSparseArray. The generic HashMap implementation can be quite memory inefficient because it needs a separate entry object for every mapping. Additionally, the SparseArray classes are more efficient because they avoid the system’s need to autobox the key and sometimes value (which creates yet another object or two per entry).
尽量使用Android frameword提供的集合来存储数据,比如SparseArray
,SparseBooleanArray
,LongSparseArray
。HashMap
的内存使用率不是很高效,它需要一个键来映射值。另外,SparseArray
不需要对基础类型装箱。
Be knowledgeable about the cost and overhead of the language and libraries you are using, and keep this information in mind when you design your app, from start to finish.
在设计、开发app过程中,要对所有的内存占用心知肚明,比如你使用的库,你使用的语言等。比如尽量避免使用Enum
,HashMap
,和避免使用Abstract Method。
abstractions come at a significant cost: generally they require a fair amount more code that needs to be executed, requiring more time and more RAM for that code to be mapped into memory.
The ProGuard tool shrinks, optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and methods with semantically obscure names. Using ProGuard can make your code more compact, requiring fewer RAM pages to be mapped.
配置ProGuard,它会优化你的代码。
Use zipalign on your final APK
使用zipalign工具优化你的final apk,在gradle中配置一下就OK了。
There are two basic rules for writing efficient code:
该小节主要介绍一些在编码过程中的一些tips,做些微小的优化micro-optimizations。
Avoid Creating Unnecessary Objects
尽量避免创建不必要的对象
If you don’t need to access an object’s fields, make your method static. Invocations will be about 15%-20% faster. It’s also good practice, because you can tell from the method signature that calling the method can’t alter the object’s state.
一个类的方法如果不引用该类的成员变量,可以把该方法设置为 static
静态方法,这会提高运行速度15%~20%。
Use Static Final For Constants
常量用 static
和 Final
修饰
Avoid Internal Getters/Setters
在一个类中使用该类的成员变量时,避免使用Getters/Setters
。因为在C++中编译器会inline这些代码,而在Android上,你应该直接访问该变量。
you should use the enhanced for loop by default, but consider a hand-written counted loop for performance-critical ArrayList iteration.
官网 有示例说明。你应该第一考虑使用高级for循环来遍历集合、数组;但是在遍历ArrayList时应该 手写计数循环。
Consider Package Instead of Private Access with Private Inner Classes
If you’re using code like this in a performance hotspot, you can avoid the overhead by declaring fields and methods accessed by inner classes to have package access, rather than private access. Unfortunately this means the fields can be accessed directly by other classes in the same package, so you shouldn’t use this in public API.
将私有内部类放在同一包下,并将所需成员变量访问权限修改为Package Access,即 default
。但同时在同一包下其他类也能访问该变量,所以不应该把这种方法暴漏在Public API中。
Avoid Using Floating-Point. As a rule of thumb, floating-point is about 2x slower than integer on Android-powered devices.
避免使用浮点数。在Android设备上,浮点数运算比整数运算慢2倍。
Know and Use the Libraries
熟悉并且使用framework提供的方法,尽量不要自己写。
Developing your app with native code using the Android NDK isn’t necessarily more efficient than programming with the Java language. For one thing, there’s a cost associated with the Java-native transition
小心使用JNI。使用NDK开发并不比使用Java开发效率高,反而会在JNI上面耗费性能。
结论:
Make sure you can accurately measure your existing performance, or you won’t be able to measure the benefit of the alternatives you try.
确保你的优化是可以被证据支持的,是有数据证明的。
使用 Hierarchy Viewer 来检查布局视图,去除布局嵌套,节约性能。
It is always good practice to run the lint tool on your layout files to search for possible view hierarchy optimizations.
在开发过程中使用Lint工具检查代码。Android Studio默认开启Lint检查。
Reusing layouts is particularly powerful as it allows you create reusable complex layouts.
使用 <include>
复用组件。
The tag helps eliminate redundant view groups in your view hierarchy when including one layout within another.
when you include this layout in another layout (using the tag), the system ignores the element and places the two buttons directly in the layout, in place of the tag.
<merge>
标签用来减少冗余的布局嵌套,如果父布局都是同一个的话,此标签可以代替<include>
标签来复用组件。
Sometimes your layout might require complex views that are rarely used. Whether they are item details, progress indicators, or undo messages, you can reduce memory usage and speed up rendering by loading the views only when they are needed.
ViewStub is a lightweight view with no dimension and doesn’t draw anything or participate in the layout. As such, it’s cheap to inflate and cheap to leave in a view hierarchy
One drawback of ViewStub is that it doesn’t currently support the tag in the layouts to be inflated.
使用 ViewStub
标签来承载那些很复杂而又很少用到的View( item details、进度条等等),该标签没有尺寸,并且不用被渲染,所有可以减少内存占用,提高渲染效率。当需要该标签的内容出现时,调用该标签对象的findViewById(R.id.viewStub).inflate()
方法或者findViewById(R.id.viewStub).setVisibility(View.Visible)
就可以加载该布局。
keep the application’s main thread (the UI thread) free from heavy processing. Ensure you do any disk access, network access, or SQL access in a separate thread. To test the status of your app, you can enable StrictMode
避免主线程耗时操作,你可以开启StrictMode
检查你的代码。
太多了…
多线程操作,这个比较熟悉
Generally, the system displays an ANR if an application cannot respond to user input.
In any situation in which your app performs a potentially lengthy operation, you should not perform the work on the UI thread, but instead create a worker thread and do most of the work there.
在应用未响应用户操作时,系统会报出ANR异常。通常有如下情况:5秒内应用无响应;广播内耗时10秒还没完成。
所有你不应该把耗时操作放在UI线程,而应该放在 a worker thread里执行。
如何避免呢?
- any method that runs in the UI thread should do as little work as possible on that thread. In particular, activities should do as little as possible to set up in key life-cycle methods such as onCreate() andonResume(). Potentially long running operations such as network or database operations, or computationally expensive calculations such as resizing bitmaps should be done in a worker thread (or in the case of databases operations, via an asynchronous request).
所有在UI线程的操作应该越少越好。尤其是,Activity的关键生命周期方法onCreate()
方法和onResume()
方法内要少消耗时间,和网络请求、数据库访问、复杂的计算(对bitmap的操作)等这些操作必须要放在worker Thread中执行。
*Although it’s more complicated than AsyncTask
, you might want to instead create your own Thread or HandlerThread class. If you do, you should set the thread priority to “background” priority by callingProcess.setThreadPriority() and passing THREAD_PRIORITY_BACKGROUND. If you don’t set the thread to a lower priority this way, then the thread could still slow down your app because it operates at the same priority as the UI thread by default.*
一般可以使用 AsyncTask
来完成,但如果你想使用Thread
或HandlerThread
,请注意要将该线程的优先级设置为 THREAD_PRIORITY_BACKGROUND
。否则该线程的优先级和UI线程一样,那这些耗时操作一样会造成卡顿。
If your application is doing work in the background in response to user input, show that progress is being made such as with a ProgressBar in your UI.
Use performance tools such as Systrace and Traceview to determine bottlenecks in your app’s responsiveness.
可以考虑使用 ProgressBar
来加强响应。使用 Traceview
和 Systrace
来优化性能。
By default, all components of the same application run in the same process and most applications should not change this. However, if you find that you need to control which process a certain component belongs to, you can do so in the manifest file.
默认应用的所有组件全都运行在同一个进程内,但是可以通过修改manifest
来让组件运行在不同的进程内。
Android might decide to shut down a process at some point, when memory is low and required by other processes that are more immediately serving the user.
The decision whether to terminate a process, therefore, depends on the state of the components running in that process.
Android会在内存不足时按照进程现在的状态来清理进程。
进程优先级如下:
1. Foreground process 前台进程
A process that is required for what the user is currently doing. Generally, only a few foreground processes exist at any given time. They are killed only as a last resort
前台进程是当前用户正在交互的进程,优先级最高,最后被杀死。一般来说,只有很少的进程是前台进程。
2. Visible process 可见进程
A process that doesn’t have any foreground components, but still can affect what the user sees on screen.
A visible process is considered extremely important and will not be killed unless doing so is required to keep all foreground processes running.
前台进程是那些并没有前台的组件,但依旧影响着用户操作的进程。
3. Service process 服务进程
A process that is running a service that has been started with the startService() method and does not fall into either of the two higher categories. Although service processes are not directly tied to anything the user sees, they are generally doing things that the user cares about, so the system keeps them running unless there’s not enough memory to retain them along with all foreground and visible processes.
服务进程是那些调用了startService()
方法的进程。对于许多在后台做处理(如加载数据)而没有立即成为前台服务的应用都属于这种情况。
4. Background process 后台进程
A process holding an activity that’s not currently visible to the user (the activity’s onStop() method has been called). These processes have no direct impact on the user experience, and the system can kill them at any time to reclaim memory for a foreground, visible, or service process. Usually there are many background processes running, so they are kept in an LRU (least recently used) list to ensure that the process with the activity that was most recently seen by the user is the last to be killed.
If an activity implements its lifecycle methods correctly, and saves its current state, killing its process will not have a visible effect on the user experience, because when the user navigates back to the activity, the activity restores all of its visible state.
后台进程是Activity调用了onStop
后,应用对用户不可见。系统可以随时为了更高优先级的进程存活而杀死他们。通常来说会有很多后台进程,所以系统把他们存储在队列中,按照LRU算法来回收他们。当然,如果一个Acitivy能够很好的保存状态,那么在重启该应用时能够很好的恢复状态。
5. Empty process 空进程
A process that doesn’t hold any active application components. The only reason to keep this kind of process alive is for caching purposes, to improve startup time the next time a component needs to run in it.
空进程指该进程内什么都没有。保留该进程的原因是为了下次启动该App时能够很快启动。
This thread is very important because it is in charge of dispatching events to the appropriate user interface widgets, including drawing events.*
The system does not create a separate thread for each instance of a component. All components that run in the same process are instantiated in the UI thread, and system calls to each component are dispatched from that thread. Consequently, methods that respond to system callbacks(onClickEvent
) always run in the UI thread of the process.
UIThread非常重要,他管理所有与用户交互控件的事件,甚至是渲染事件。
系统创建应用进程时只开启了一个线程,即UI线程,所有组件、控件都在该线程内运行,所有事件的响应也在该线程内。
示例:
For instance, when the user touches a button on the screen, your app’s UI thread dispatches the touch event to the widget, which in turn sets its pressed state and posts an invalidate request to the event queue. The UI thread dequeues the request and notifies the widget that it should redraw itself.
When the thread is blocked, no events can be dispatched, including drawing events. From the user’s perspective, the application appears to hang.
所以不要在主线程里做耗时操作,否则会阻塞主线程。造成的结果就是Event事件没有机会被分配出去,渲染也不能被执行,对用户来说,就像是该App停止了一样。
另外Android UI控件 并不是线程安全的。所以:
多线程操作这个很熟悉了吧~~
跨进程通信,通过IPC机制进行通信。