前言
今天有时间就准备整理一下Android的四大组件,有时候经常面试、技术群、朋友会聊起来四大组件,哪四大组件到底是什么,四大组件启动顺序是啥?我觉得如果面试的时候,有人问起来这个一定要注意,其实他想考察是你对四大组件的认识和理解,那么四大组件包括:Activity、Service、Broadcast Receiver、Content Provider等,那么四大组件的启动顺序,应用启动的时候会先启动哪个里,这个时候有很多可能不关心这方面,我以前也是这样,后面我是深入了解后,才知道这是很大的坑,慎重、慎重、慎重。
一、介绍
1.Activity简介
(1)一个Activity通常就是一个单独的屏幕(窗口)。
(2)Activity之间通过Intent进行通信。
(3)android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行该Activity。
(4)一个Activity就是一个独立业务可视化界面.也是四大组件中唯一一个可以与用户交互的组件,再有如果做过ios的话。肯定对ViewController有一定了解,Activity跟ios很像。
(5)Activity就像MVC中的C的架构,业务逻辑独立层,Activity可以实现业务逻辑、也可以业务逻辑抽离,但都离不开活动。
2.Service简介
(1)service是一个服务,一个可以执行长时间任务的,并执行在后台的单元,不可去做用户交互的事情。
(2)service包括两种服务方式:本地服务、远程服务。
(3)service本地服务就是存在于主线程的服务,使用bindService方式或者startService方式, bindService如果绑定的销毁,生命周期则被销毁。startService就是平时编写容易,
不需要去写一个binder,缺点无法跟发起者业务逻辑沟通。
(4)service 远程服务是指为独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。
3.Broadcast Receiver简介
(1)Receiver是什么?首先它是一个广播,为啥一个广播都独立于四大组件,哪就如现实生活当中,如果两个人,如果一个人想起来有件事要找另一个人,那么怎么办?最好打电话,而广播就相当于手机,一个人与人之间通信,应用与应用可以通信的组件,而手机号码就是广播中的意图,可以定位找到
对方。
(2)Receiver包括两种服务方式:动态注册、静态注册。
(3)动态注册广播接收器特点是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用。
4.Content provider简介
(1)Provider是什么?首先它是一个内容共享,为啥一个广播都独立于四大组件,这个最大的好处是两个应用数据之间共享,通过一个url就可以访问对方数据。可以通过ContentResolver类从该内容提供者中获取或存入数据。
(2)ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。
(3)开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。
(4)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
二、启动顺序
1.Activity启动顺序
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Intent intent=new Intent(this,OtherActivity.class); //方法1
Intent intent2=new Intent();
intent2.setClass(this, OtherActivity.class);//方法2
intent2.setClassName(this, "com.zy.MutiActivity.OtherActivity"); //方法3 此方式可用于打开其它的应用
intent2.setComponent(new ComponentName(this, OtherActivity.class)); //方法4
startActivity(intent2);
(3)隐式调用方法(只要action、category、data和要跳转到的Activity在AndroidManifest.xml中设置的匹配就OK
2.Service启动顺序
package com.dada.test;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.dada.test.BindService.MyBinder;
public class TestActivity extends Activity {
private boolean flag;
private static final String TAG = "TestActivity";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btnStart = (Button) findViewById(R.id.btnStart);
Button btnStop = (Button) findViewById(R.id.btnStop);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动service 方式2
bindService();
}
});
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//停止service 方式2
unBindService();
}
});
}
//启动service 方式2
//
private void bindService(){
Intent intent = new Intent(TestActivity.this,BindService.class);
Log.i(TAG, "bindService()");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
private void unBindService(){
Log.i(TAG, "unBindService() start....");
if(flag == true){
Log.i(TAG, "unBindService() flag");
unbindService(conn);
flag = false;
}
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
Log.i(TAG, "onServiceDisconnected()");
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
Log.i(TAG, "onServiceConnected()");
MyBinder binder = (MyBinder)service;
BindService bindService = binder.getService1();
bindService.MyMethod();
flag = true;
}
};
}
3.Broadcast Receiver启动顺序
1).静态注册
<receiver android:name=".MyBroadcastReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
其中,intent-filter由于指定此广播接收器将用于接收特定的广播类型。本示例中给出的是用于接收网络状态改变或开启启动时系统自身所发出的广播。当此App首次启动时,系统会自动实例化MyBroadcastReceiver,并注册到系统中。
之前常说:静态注册的广播接收器即使app已经退出,主要有相应的广播发出,依然可以接收到,但此种描述自Android 3.1开始有可能不再成立,具体分析详见本文后面部分。
2).动态注册:
动态注册时,无须在AndroidManifest中注册<receiver/>组件。直接在代码中通过调用Context的registerReceiver函数,可以在程序中动态注册BroadcastReceiver。registerReceiver的定义形式如下:
1 registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
2 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
典型的写法示例如下:
1 public class MainActivity extends Activity {
2 public static final String BROADCAST_ACTION = "com.example.corn";
3 private BroadcastReceiver mBroadcastReceiver;
4
5 @Override
6 protected void onCreate(Bundle savedInstanceState) {
7 super.onCreate(savedInstanceState);
8 setContentView(R.layout.activity_main);
9
10 mBroadcastReceiver = new MyBroadcastReceiver();
11 IntentFilter intentFilter = new IntentFilter();
12 intentFilter.addAction(BROADCAST_ACTION);
13 registerReceiver(mBroadcastReceiver, intentFilter);
14 }
15
16 @Override
17 protected void onDestroy() {
18 super.onDestroy();
19 unregisterReceiver(mBroadcastReceiver);
20 }
21
22 }
注:Android中所有与观察者模式有关的设计中,一旦涉及到register,必定在相应的时机需要unregister。因此,上例在onDestroy()回到中需要unregisterReceiver(mBroadcastReceiver)。
当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中。当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
3.广播发送及广播类型
经常说”发送广播“和”接收“,表面上看广播作为Android广播机制中的实体,实际上这一实体本身是并不是以所谓的”广播“对象存在的,而是以”意图“(Intent)去表示。定义广播的定义过程,实际就是相应广播”意图“的定义过程,然后通过广播发送者将此”意图“发送出去。被相应的BroadcastReceiver接收后将会回调onReceive()函数。
下段代码片段显示的是一个普通广播的定义过程,并发送出去。其中setAction(..)对应于BroadcastReceiver中的intentFilter中的action。
1 Intent intent = new Intent();
2 intent.setAction(BROADCAST_ACTION);
3 intent.putExtra("name", "qqyumidi");
4 sendBroadcast(intent);
根据广播的发送方式,可以将其分为以下几种类型:
1.Normal Broadcast:普通广播
2.System Broadcast: 系统广播
3.Ordered broadcast:有序广播
4.Sticky Broadcast:粘性广播(在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated)
5.Local Broadcast:App应用内广播
下面分别总结下各种类型的发送方式及其特点。
1).Normal Broadcast:普通广播
此处将普通广播界定为:开发者自己定义的intent,以context.sendBroadcast_"AsUser"(intent, ...)形式。具体可以使用的方法有:
sendBroadcast(intent)/sendBroadcast(intent, receiverPermission)/sendBroadcastAsUser(intent, userHandler)/sendBroadcastAsUser(intent, userHandler,receiverPermission)。
普通广播会被注册了的相应的感兴趣(intent-filter匹配)接收,且顺序是无序的。如果发送广播时有相应的权限要求,BroadCastReceiver如果想要接收此广播,也需要有相应的权限。
2).System Broadcast: 系统广播
Android系统中内置了多个系统广播,只要涉及到手机的基本操作,基本上都会发出相应的系统广播。如:开启启动,网络状态改变,拍照,屏幕关闭与开启,点亮不足等等。每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,有系统自动发出。
3)Ordered broadcast:有序广播
有序广播的有序广播中的“有序”是针对广播接收者而言的,指的是发送出去的广播被BroadcastReceiver按照先后循序接收。有序广播的定义过程与普通广播无异,只是其的主要发送方式变为:sendOrderedBroadcast(intent, receiverPermission, ...)。
对于有序广播,其主要特点总结如下:
1>多个具当前已经注册且有效的BroadcastReceiver接收有序广播时,是按照先后顺序接收的,先后顺序判定标准遵循为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序,对于具有相同的priority的动态广播和静态广播,动态广播会排在前面。
2>先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。
4)Sticky Broadcast:粘性广播(在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated)。
既然已经deprecated,此处不再多做总结。
5)Local Broadcast:App应用内广播(此处的App应用以App应用进程为界)
由前文阐述可知,Android中的广播可以跨进程甚至跨App直接通信,且注册是exported对于有intent-filter的情况下默认值是true,由此将可能出现安全隐患如下:
1.其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
2.其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。
无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是:
1.对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;
2.在广播发送和接收时,都增加上相应的permission,用于权限验证;
3.发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定在,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个App。实际的业务需求中,App应用内广播确实可能需要用到。同时,之所以使用应用内广播时,而不是使用全局广播的形式,更多的考虑到的是Android广播机制中的安全性问题。
相比于全局广播,App应用内广播优势体现在:
1.安全性更高;
2.更加高效。
为此,Android v4兼容包中给出了封装好的LocalBroadcastManager类,用于统一处理App应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调context变成了LocalBroadcastManager的单一实例。
代码片段如下:
1 //registerReceiver(mBroadcastReceiver, intentFilter);
2 //注册应用内广播接收器
3 localBroadcastManager = LocalBroadcastManager.getInstance(this);
4 localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
5
6 //unregisterReceiver(mBroadcastReceiver);
7 //取消注册应用内广播接收器
8 localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
9
10 Intent intent = new Intent();
11 intent.setAction(BROADCAST_ACTION);
12 intent.putExtra("name", "qqyumidi");
13 //sendBroadcast(intent);
14 //发送应用内广播
15 localBroadcastManager.sendBroadcast(intent);
4.不同注册方式的广播接收器回调onReceive(context, intent)中的context具体类型
1).对于静态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是ReceiverRestrictedContext;
2).对于全局广播的动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Activity Context;
3).对于通过LocalBroadcastManager动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Application Context。
注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册的ContextReceiver才有可能接收到(静态注册或其他方式动态注册的ContextReceiver是接收不到的)。
5.不同Android API版本中广播机制相关API重要变迁
1).Android5.0/API level 21开始粘滞广播和有序粘滞广播过期,以后不再建议使用;
2).”静态注册的广播接收器即使app已经退出,主要有相应的广播发出,依然可以接收到,但此种描述自Android 3.1开始有可能不再成立“
Android 3.1开始系统在Intent与广播相关的flag增加了参数,分别是FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES。
FLAG_INCLUDE_STOPPED_PACKAGES:包含已经停止的包(停止:即包所在的进程已经退出)
FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已经停止的包
主要原因如下:
自Android3.1开始,系统本身则增加了对所有app当前是否处于运行状态的跟踪。在发送广播时,不管是什么广播类型,系统默认直接增加了值为FLAG_EXCLUDE_STOPPED_PACKAGES的flag,导致即使是静态注册的广播接收器,对于其所在进程已经退出的app,同样无法接收到广播。
详情参加Android官方文档:http://developer.android.com/about/versions/android-3.1.html#launchcontrols
由此,对于系统广播,由于是系统内部直接发出,无法更改此intent flag值,因此,3.1开始对于静态注册的接收系统广播的BroadcastReceiver,如果App进程已经退出,将不能接收到广播。
但是对于自定义的广播,可以通过复写此flag为FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的BroadcastReceiver,即使所在App进程已经退出,也能能接收到广播,并会启动应用进程,但此时的BroadcastReceiver是重新新建的。
1 Intent intent = new Intent();
2 intent.setAction(BROADCAST_ACTION);
3 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
4 intent.putExtra("name", "qqyumidi");
5 sendBroadcast(intent);
注1:对于动态注册类型的BroadcastReceiver,由于此注册和取消注册实在其他组件(如Activity)中进行,因此,不受此改变影响。
注2:在3.1以前,相信不少app可能通过静态注册方式监听各种系统广播,以此进行一些业务上的处理(如即时app已经退出,仍然能接收到,可以启动service等..),3.1后,静态注册接受广播方式的改变,将直接导致此类方案不再可行。于是,通过将Service与App本身设置成不同的进程已经成为实现此类需求的可行替代方案。
4.Content provider启动顺序
既然涉及到IPC通信,那么ContentProvider一定继承自IInterface,这个IInterface就是IContentProvider,其主要的接口方法如下,
public IBulkCursor bulkQuery(Uri url, String[] projection,
String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
CursorWindow window) throws RemoteException;
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException;
public String getType(Uri url) throws RemoteException;
public Uri insert(Uri url, ContentValues initialValues)
throws RemoteException;
public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException;
public int delete(Uri url, String selection, String[] selectionArgs)
throws RemoteException;
public int update(Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException;
public ParcelFileDescriptor openFile(Uri url, String mode)
throws RemoteException, FileNotFoundException;
public AssetFileDescriptor openAssetFile(Uri url, String mode)
throws RemoteException, FileNotFoundException;
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
但是阅读ContentProvider代码时,会发现
public abstract boolean onCreate();
public void onConfigurationChanged(Configuration newConfig);
public void onLowMemory();
这几个方法并不在IContentProvider中声明,因此千万注意它们在运行时并不是处在AndroidManifest.xml中声明时的进程中。而是处在调用ContentProvider的应用的进程中的。
因此千万不要在onCreate中添加你期望运行在ContentProvider的进程的代码。