Developing for Android V: The Rules: Language and Libraries
2015-06-07 20:46
274 查看
This section covers practices around the Java programming language and core libraries.
Traditional collections classes tend to be sub-optimal on Android due to the overhead of allocations (which runs into Garbage Collector concerns, as discussed in the previous Memory chapter). Collections classes written specifically for Android are better in
most cases, such as ArrayMap (prefer over HashMap)
and SparseArray. The more general classes are
still appropriate when collections are particularly large, but smaller collections benefit from avoiding autoboxing and other allocation concerns.
There are several issues and options around serialization formats, which are discussed separately in the sections below.
Parcelable is Android’s IPC serialization format,
or rather, it is an interface for writing out your data so it can be sent over Binder.
It does have limits:
It is not safe to write Parcels to disk.
You can implement your own Parcelables, but if the receiving process doesn’t have access to the same classes, unparceling will fail. (This is true of Parcels you send to the framework, too.)
Some types of objects (bitmaps and CursorWindows) placed into Parcels are instead written to ashmem (Android shared memory) files, and a file descriptor is written into the Parcel in their place. This is an important performance optimization but can hide the
true memory cost of big Parcels (and will take up space in ashmem until unparceled).
Starting in API 21, there is a new PersistableBundle class
that is a variant of Bundle with a stable data format that supports serialization as XML. It only accepts a subset of the data types supported by Bundle. In particular, it does not support Parcelable objects.
PersistableBundles are particularly useful when working with data that is being passed through the Binder IPC, which requires Bundles to begin with.
Serializable, ObjectOutputStream, and friends will all work, but these approaches have very significant overhead, which increases with the raw number of fields being serialized. For example, serialization entails storing extra information that is necessary
for it being a potentially long-lived disk format, but which is unnecessary overhead otherwise. There are almost always better options to use where you can more carefully control the information that is being stored:
Use Parcelable for anything that needs to be exchanged between running processes.
SharedPreferences is a good, quick
key-value store for situations that do not require large numbers of elements.
Use SQLite for more complex,
row-oriented data.
There is still a specific case where Java serialization may be required: if your app is talking to legacy servers that require that specific protocol. In that case, you should be looking into upgrading the system to use a more efficient mechanism.
These text-based formats tend to be slower and more verbose than other options, so they are poorly suited for large, complex data, IPC (use Parcelable instead), or data that needs to be queried (use SQLite). Integrating with web services that speak JSON or
XML is fine. It’s also okay to use a little bit of XML to store a small amount of rarely-modified data (although SharedPreferences might
be even easier).
Note that the XML used in Android resource files is compiled at build time into a compact format that is more efficiently parsed at runtime. The advice here against using XML is specific to parsing actual runtime XML data.
JNI is problematic for many reasons. For one thing, the native code required for JNI must be compiled for all architectures (ARM, ARM64, MIPS, etc.), unlike the normal Java programming language code which runs across all Android platforms. Also, there is a
high performance penalty for JNI. Crossing the JNI barrier (both calls down into native code as well as calls back up from native code) is quite expensive, far exceeding the relatively insignificant overhead of method calls above the native layer. Finally,
there is increased potential for difficult bugs due to non-obvious semantics of memory accesses at the native layer.
But if you do have to use JNI:
Use long for pointers to ensure 64-bit compatibility.
Native methods should almost always be static, with the native object pointer passed as first argument.
The Java programming language object should determine the lifetime of its native peer, not the other way around.
Beware of global object reference cycles causing leaks.
Check arguments before calling into JNI rather than down in JNI where it’s tedious and error prone.
Minimize the number of JNI crossings. Try to do more work per JNI call to amortize the overhead of of each call.
Pass native pointers by value instead of querying members from native. Note that this technique is only valid for non-static methods; static methods could run into problems where the object in question is collected before or while it is being used in the native
code. Instance methods do not have this problem, as there is an implicit ‘this’ reference to the object that will ensure that it is not collected during the JNI call.
Consider RenderScript Compute for computationally-intensive
operations that would benefit from it.
This was discussed previously in the section Avoid Allocations When Possibleof the Memory chapter,
but is worth noting here as well. Always use primitive types (int, float, boolean, etc.) instead of their Object equivalents (Integer, Float, Boolean, etc.) when you have the choice. The overhead in terms of memory (object instances always cost more than their
primitive type equivalents) and performance (object instances require more runtime overhead to access the underlying value) means that you should always use primitive types when you can on Android.
Generics and some data structures (e.g., the traditional collections classes) require object types. But note that there are various collections classes on Android (ArrayMap, SimpleArrayMap, SparseArray, SparseBooleanArray, SparseIntArray, SparseLongArray, and
LongSparseArray) that avoid the object type requirement for specific use cases.
Use Android-Appropriate Data Structures
Traditional collections classes tend to be sub-optimal on Android due to the overhead of allocations (which runs into Garbage Collector concerns, as discussed in the previous Memory chapter). Collections classes written specifically for Android are better inmost cases, such as ArrayMap (prefer over HashMap)
and SparseArray. The more general classes are
still appropriate when collections are particularly large, but smaller collections benefit from avoiding autoboxing and other allocation concerns.
Serialization
There are several issues and options around serialization formats, which are discussed separately in the sections below.
Parcelable
Parcelable is Android’s IPC serialization format,or rather, it is an interface for writing out your data so it can be sent over Binder.
It does have limits:
It is not safe to write Parcels to disk.
You can implement your own Parcelables, but if the receiving process doesn’t have access to the same classes, unparceling will fail. (This is true of Parcels you send to the framework, too.)
Some types of objects (bitmaps and CursorWindows) placed into Parcels are instead written to ashmem (Android shared memory) files, and a file descriptor is written into the Parcel in their place. This is an important performance optimization but can hide the
true memory cost of big Parcels (and will take up space in ashmem until unparceled).
Persistable Bundles
Starting in API 21, there is a new PersistableBundle classthat is a variant of Bundle with a stable data format that supports serialization as XML. It only accepts a subset of the data types supported by Bundle. In particular, it does not support Parcelable objects.
PersistableBundles are particularly useful when working with data that is being passed through the Binder IPC, which requires Bundles to begin with.
Avoid Java Serialization
Serializable, ObjectOutputStream, and friends will all work, but these approaches have very significant overhead, which increases with the raw number of fields being serialized. For example, serialization entails storing extra information that is necessaryfor it being a potentially long-lived disk format, but which is unnecessary overhead otherwise. There are almost always better options to use where you can more carefully control the information that is being stored:
Use Parcelable for anything that needs to be exchanged between running processes.
SharedPreferences is a good, quick
key-value store for situations that do not require large numbers of elements.
Use SQLite for more complex,
row-oriented data.
There is still a specific case where Java serialization may be required: if your app is talking to legacy servers that require that specific protocol. In that case, you should be looking into upgrading the system to use a more efficient mechanism.
XML and JSON
These text-based formats tend to be slower and more verbose than other options, so they are poorly suited for large, complex data, IPC (use Parcelable instead), or data that needs to be queried (use SQLite). Integrating with web services that speak JSON orXML is fine. It’s also okay to use a little bit of XML to store a small amount of rarely-modified data (although SharedPreferences might
be even easier).
Note that the XML used in Android resource files is compiled at build time into a compact format that is more efficiently parsed at runtime. The advice here against using XML is specific to parsing actual runtime XML data.
Avoid JNI
JNI is problematic for many reasons. For one thing, the native code required for JNI must be compiled for all architectures (ARM, ARM64, MIPS, etc.), unlike the normal Java programming language code which runs across all Android platforms. Also, there is ahigh performance penalty for JNI. Crossing the JNI barrier (both calls down into native code as well as calls back up from native code) is quite expensive, far exceeding the relatively insignificant overhead of method calls above the native layer. Finally,
there is increased potential for difficult bugs due to non-obvious semantics of memory accesses at the native layer.
But if you do have to use JNI:
Use long for pointers to ensure 64-bit compatibility.
Native methods should almost always be static, with the native object pointer passed as first argument.
The Java programming language object should determine the lifetime of its native peer, not the other way around.
Beware of global object reference cycles causing leaks.
Check arguments before calling into JNI rather than down in JNI where it’s tedious and error prone.
Minimize the number of JNI crossings. Try to do more work per JNI call to amortize the overhead of of each call.
Pass native pointers by value instead of querying members from native. Note that this technique is only valid for non-static methods; static methods could run into problems where the object in question is collected before or while it is being used in the native
code. Instance methods do not have this problem, as there is an implicit ‘this’ reference to the object that will ensure that it is not collected during the JNI call.
Consider RenderScript Compute for computationally-intensive
operations that would benefit from it.
Prefer Primitive Types
This was discussed previously in the section Avoid Allocations When Possibleof the Memory chapter,but is worth noting here as well. Always use primitive types (int, float, boolean, etc.) instead of their Object equivalents (Integer, Float, Boolean, etc.) when you have the choice. The overhead in terms of memory (object instances always cost more than their
primitive type equivalents) and performance (object instances require more runtime overhead to access the underlying value) means that you should always use primitive types when you can on Android.
Generics and some data structures (e.g., the traditional collections classes) require object types. But note that there are various collections classes on Android (ArrayMap, SimpleArrayMap, SparseArray, SparseBooleanArray, SparseIntArray, SparseLongArray, and
LongSparseArray) that avoid the object type requirement for specific use cases.
相关文章推荐
- android实现边框圆角
- Android内核开发:图解Android系统的启动过程
- Android内核开发:图解Android系统的启动过程
- Android应用AsyncTask处理机制详解及源码分析
- Android Bitmap回收 注意事项
- Android应用层View绘制流程与源码分析
- android学习笔记(一)activity的基本用法和创建,bundle如何传递数据,intent的用法
- Android提权漏洞分析——rageagainstthecage
- Android 优化电池使用时间——确定和监控基座对接状态和类型
- android中解析doc、docx、xls、xlsx格式文件
- Android中ListView.getCount()与ListView.getChildCount()区别
- android实现MP3播放器
- Android开发资源获取国内代理(转载)
- Android中实现异步任务机制方式:AsyncTask
- 新建了CSDN博客,希望记录我的android学习历程
- Android 优化电池使用时间 ——监控电池电量和充电状态
- android 选择图片 剪裁 拍照 兼容所有版本的代码
- Developing for Android, IV: The Rules: Networking
- 【Android 个人理解(九)】Activity的生命周期方法的深入理解
- Developing for Android, III: The Rules: Performance