Android基本组件
Android 基本组件指Activity 、Fragment 、Service 、BroadcastReceiver 、ContentProvider 等等。
其它相关文章:
Android编程规范摘要1 (资源文件命名与使用)
Android编程规范摘要2 (基本组件)
Android编程规范摘要3 (UI与布局)
Android编程规范摘要4 (进程、线程与消息通信)
Android编程规范摘要5 (文件与数据库)
Android编程规范摘要6 (Bitmap、Drawable 与动画)
Android编程规范摘要7 (安全)
[强制] Activity 间的数据通信,对于数据量比较大的,避免使用Intent + Parcelable 的方式,可以考虑EventBus 等替代方案,以免造成TransactionTooLargeException。
Activity#onSaveInstanceState()
方法不是Activity 生命周期方法,也不保证一定会被调用。- 它是用来在Activity 被意外销毁时保存UI 状态的,只能用于保存临时性数据,例如UI 控件的属性等,不能跟数据的持久化存储混为一谈。持久化存储应该在Activity#onPause()/onStop()中实行。
[强制] Activity间通过隐式Intent的跳转,在发出Intent之前必须通过resolveActivity 检查,避免找不到合适的调用组件,造成ActivityNotFoundException 的异常:
if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null)
[强制] 避免在
Service#onStartCommand()
/onBind()
方法中执行耗时操作,如果确实有需求,应改用IntentService 或采用其他异步机制完成。[强制] 避免在
BroadcastReceiver#onReceive()
中执行耗时操作,如果有耗时工作,应该创建IntentService 完成,而不应该在BroadcastReceiver 内创建子线程去做。- 由于该方法是在主线程执行,如果执行耗时操作会导致UI 不流畅。
- 正确方式: IntentService 、创建HandlerThread 或者调用
Context#registerReceiver(BroadcastReceiver, IntentFilter,String, Handler)
方法,在其他Wroker 线程执行onReceive
方法。
[强制] 避免使用隐式Intent 广播敏感信息,信息可能被其他注册了对应 BroadcastReceiver 的App 接收。
Context#sendBroadcast()
: 会被所有感兴趣的receiver 接收。Context#sendOrderedBroadcast()
: 优先级高的恶意 receiver 甚至可能直接丢弃广播,造成服务不可用。LocalBroadcastManager#sendBroadcast()
: 如果广播仅限于应用内部,使用这种方式发送广播,可以避免泄漏/拦截风险。
添加 Fragment 时,确保
FragmentTransaction#commit()
在Activity#onPostResume()
或者FragmentActivity#onResumeFragments()
内调用。- 不要随意使用
FragmentTransaction#commitAllowingStateLoss()
来代替,任何commitAllowingStateLoss()
的使用必须经过code review,确保无负面影响。 风险: Activity 可能因为各种原因被销毁, Android 支持页面被销毁前通过
Activity#onSaveInstanceState()
保存自己的状态。但如果FragmentTransaction.commit()
发生在Activity 状态保存之后,就会导致Activity 重建、恢复状态时无法还原页面状态,从而可能出错,系统会抛出IllegalStateExceptionStateLoss
异常。推荐做法: 在Activity 的
onPostResume()
或onResumeFragments()
(FragmentActivity)里面执行FragmentTransaction.commit(),
如有必要也可在onCreate()
里执行。警告: 不要随意改用FragmentTransaction.commitAllowingStateLoss() 或者直接使用try-catch 避免
crash,这不是问题的根本解决之道。当且仅当你确认Activity 重建、恢复状态时,本次commit 丢失不会造成影响时才可这么做。
- 不要随意使用
不要在Activity#onDestroy()内执行释放资源的工作,例如一些工作线程的销毁和停止,因为onDestroy()执行的时机可能较晚。
推荐: 可根据实际需要,在Activity#onPause()/onStop()中结合isFinishing()的判断来执行。如非必须,避免使用嵌套的Fragment。
嵌套Fragment 是在Android API 17添加到SDK以及Support 库中的功能,Fragment嵌套使用会有一些坑,容易出现bug,比较常见的问题有如下几种:
- onActivityResult()方法的处理错乱,内嵌的Fragment 可能收不到该方法的回调,需要由宿主Fragment 进行转发处理;
- 突变动画效果;
- 被继承的setRetainInstance(),导致在Fragment 重建时多次触发不必要的逻辑。
总是使用显式Intent 启动或者绑定Service,且不要为服务声明Intent Filter,保证应用的安全性。
- 例外推荐: 如果确实需要使用隐式调用,则可为Service 提供Intent Filter
并从Intent 中排除相应的组件名称,但必须搭配使用Intent#setPackage()方法设置Intent 的指定包名,这样可以充分消除目标服务的不确定性。
- 例外推荐: 如果确实需要使用隐式调用,则可为Service 提供Intent Filter
Service 需要以多线程来并发处理多个启动请求,建议使用IntentService,可避免各种复杂的设置。
- Service 组件一般运行主线程,应当避免耗时操作,如果有耗时操作应该在Worker线程执行。可以使用IntentService 执行后台任务。
正例:
public class SingleIntentService extends IntentService { public SingleIntentService() { super("single-service thread"); } @Override protected void onHandleIntent(Intent intent) { try { ...... } catch (InterruptedException e) { e.printStackTrace(); } } }
反例:
public class HelloService extends Service { ... @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { //操作语句 } }).start(); ... } }
对于只用于应用内的广播,优先使用
LocalBroadcastManager
来进行注册
和发送,LocalBroadcastManager
安全性更好,同时拥有更高的运行效率。- 注册:
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter);
- 注销:
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
- 发送:
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
- 全局广播风险,请参考第六条。
- 注册:
当前Activity 的onPause 方法执行结束后才会创建(onCreate)或恢复
(onRestart)别的Activity,所以在onPause 方法中不适合做耗时较长的工作,这
会影响到页面之间的跳转效率。[强制] Activity 或者Fragment 中动态注册
BroadCastReceiver
时,registerReceiver()
和unregisterReceiver()
要成对出现。- 风险: 已经注册的receiver没有在合适的时机注销,可能会导致内存泄漏,占用内存空间,加重SystemService负担。
- 崩溃: 部分华为的机型会对receiver 进行资源管控,单个应用注册过多receiver 会触发管控模块抛出异常,应用直接崩溃。
- 位置: Activity 的生命周期不对应,可能出现多次onResume 造成receiver 注册多个,但最终只注销一个,其余receiver 产生内存泄漏。
- 正例: 在
onResume
中注册, 在onPause
中注销。
[强制] Android 基础组件如果使用隐式调用,应在 AndroidManifest.xml 中使用
<intent-filter>
或在代码中使用 IntentFilter 增加过滤。- 风险: 如果浏览器支持Intent Scheme Uri语法,如果过滤不当,那么恶意用户可能通过浏览器js 代码进行一些恶意行为,比如盗取cookie 等。如果使用了
Intent.parseUri
函数,获取的intent 必须严格过滤。 - 正例:
// 将intent scheme URL 转换为intent 对象
Intent intent = Intent.parseUri(uri);
// 禁止没有BROWSABLE category 的情况下启动activity
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);
// 使用intent 启动activity
context.startActivityIfNeeded(intent, -1)
- 反例:
Intent intent = Intent.parseUri(uri.toString().trim().substring(15), 0);
intent.addCategory("android.intent.category.BROWSABLE");
context.startActivity(intent);
- 风险: 如果浏览器支持Intent Scheme Uri语法,如果过滤不当,那么恶意用户可能通过浏览器js 代码进行一些恶意行为,比如盗取cookie 等。如果使用了