学习笔记之Android四大核心组件详解

时间:2021-12-01 04:35:50

概述

Android四大核心组件指的是Activity,Service,ContentProvider,BroadCastReceiver,核心组件都是由Android系统进行管理和维护的,一般都要在清单文件中进行注册或者在代码中动态注册。

Activity

  1. 定义与作用: Activity的中文意思是活动,代表手机屏幕的一屏,或是平板电脑中的一个窗口,提供了和用户交互的可视化界面。Activity是用于处理UI相关业务的,比如加载界面、监听用户操作事件。
  2. 生命周期: 生命周期指的是Activity从创建到销毁所执行的一系列方法,主要包括7个生命周期方法。详细流程如下图
    学习笔记之Android四大核心组件详解

    里面涉及了Activity的四个重要状态,如下表
    学习笔记之Android四大核心组件详解
    注:以上两个图表皆摘自《Android从入门到精通》。

  3. 创建与配置 创建一个Activity需继承自android.app.Activity这个类,然后重写onCreate(),在onCreate()里面调用setContentView(参数)来加载布局,参数就是布局文件。
    配置则需要在清单文件的Application节点下面注册Actvitiy,如果要首先启动该Activity则添加带有category节点且值为LAUNCHER的intent-filter节点,下面就是清单文件的配置。
  <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >

<activity
android:name=".MainActivity"
android:label="@string/app_name" >

<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

4 . 启动模式
Activity的启动模式决定了激活Activity时,是否创建新的对象,进而将影响到任务栈也叫回退栈。
在AndroidManifest.xml文件中,可以为每个activity节点配置android:launchMode属性,以决定该Activity的启动模式,该属性的值有:
—–standard:(默认值)标准模式:每次激活Activity时,都会创建新的Activity对象
—–singleTop:栈顶时唯一,即当Activity处于栈顶位置时,每次激活并不会创建新的Activity对象。但不在栈顶时,每次激活时会创建新的对象。
—–singleTask:任务栈中唯一,即当栈中没有该Activity时,将创建该Activity对象,当栈中已经有该Activity时,将不会创建新的对象,原本栈中位于该Activity之上的其它Activity将全部被强制出栈,且被激活的Activity将自动获得栈顶位置。
—–singleInstance:实例(对象)唯一,确保该Activity的对象一定只有1个,被设置为singleInstance的Activity将被置于一个专门的任务栈中,且该任务栈中有且仅有一个Activity。
什么是任务栈(回退栈):
任务栈是用来存放所有激活了的Activity对象,激活的Acitvity将会按照后进先出的栈结构显示出来。因为屏幕只能显示一个Activity,当有新的Activity被激活时,原来正在显示的Activity就会进行压栈操作被压到新Activity对象下方的位置。当按下”Back”键时栈顶Activity会执行弹栈操作,而在第2位的Activity将获得栈顶位置,显示在前台。

service

  1. 定义与作用
    — Service(服务)是一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service,并且当用户切换到另外的应用场景,Service将持续在后台运行。另外,一个组件能够绑定到一个service与之交互(IPC机制),例如,一个service可能会处理网络操作,播放音乐,操作文件I/O或者与内容提供者(content provider)交互,所有这些活动都是在后台进行,以上是Google文档的解释,资料来源于大神博客
    — Service还有一个作用就是提升进程(每个应用都是一个进程)的优先级,进程的优先级指的是在Android系统中,会把正在运行的应用确定一个优先级,当内存空间不足时,系统会根据进程的优先级清理掉一部分进程占用的内存空间,以获得足够的内存空间以供新启用的应用运行。详细的进程优先级划分如下,
    1)前台进程:应用程序存在Activity正位于前台,可见并可控
    2)可见进程:应用程序存在Activity处于局部可见状态,即局部可见却不可控
    3)服务进程:应用程序存在正在运行的Service
    4)后台进程:应用程序的所有Activity均被置于后台,没有任何Activity可见
    5) 空进程:已经退出的应用程序
    service的进程优先级详细介绍请参考这篇博文,点此进入

2 . 状态
1)启动
【启动service】
用Context类定义的startService(Intent)即可启动Service组件,其中intent定义方法与跳转Activity类似,只需把Actvity类换成Service类即可。其生命周期为启动时onCreate()–>onStartCommand()–>销毁时onDestroy(), 反复调用startService()只会导致Service反复执行onStartCommand()
【停止service】
调用Context类定义的stopService(Intent)即可停止Service组件,反复调用并没有任何效果,亦不会报告错误,即:即使停止没有启动的Service也不会出错。也可以在Service类的内部,调用Service定义的stopSelf()方法,停止当前Service。

2)绑定
主要作用是实现组件间的通信,实质的表现是Activity可以调用Service中的方法,使Service执行特定的业务,并且这些方法可以是带返回值的方法,进而Activity可以通过获取这些返回值,这样就实现与Service的通信。
【生命周期】
– onCreate() -> 当第1次绑定时执行
– onBind() -> 当第1次绑定时执行
– onDestroy() -> 当解除绑定时执行

【绑定与解绑】
调用bindService()方法可以实现Activity与Service的绑定,调用unbindService()可以解除绑定。在Activity被销毁之前,必须解除与Service的绑定。
具体实现代码如下:

//在Activity中调用bindService()来实现服务绑定
Intent intent =new Intent(this,MyService.class);
//用于连接的对象,相当于组件间的一个连接纽带
ServiceConnection conn=new ServiceConnection(){
@Override
public void onServiceConnected(
ComponentName name,
IBinder service) {
// 当Service已经连接
//参数IBinder service就是与service进行通信的对象,通过这个对象可以调用Service里的方法

}
@Override
public void onServiceDisconnected(ComponentName name) {
// 当Service断开连接
}
};
//绑定标志
int FLAGS=BIND_AUTO_CREATE;
bindService(intent,conn,FLAGS);
//然后在Activity销毁时解绑,这里需要一个连接对象,所以需要在上面把conn设为全局变量
@Override
protected void onDestroy() {
unbindService(conn);
super.onDestroy();
}

//MyService类需要继承自Service,还需要在清单文件中注册
public class MyService extends Service {

@Override
public void onCreate() {
}


@Override
public IBinder onBind(Intent intent) {
//这里需要返回一个IBinder对象,我们可以创建一个内部类来获得这个对象
MyBinder binder = new MyBinder();
return binder;
}

/**这个内部类就是我们返回的IBinder类
Activity的conn里连接上后就是得到这个对象才实现了组件间的通信
*/

public class MyBinder extends Binder {
//这个内部类可以写很多方法,来让Activity调用,还可以是带有返回值的方法
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
}


}

下图形象地说明了Service两种状态的生命周期
学习笔记之Android四大核心组件详解

3.service特性
【1】Service的粘性:
当Service被意外终止(非正常停止,即不是通过stopService()或stopSelf()停止)后,会在未来的某一刻自动重启。
Service的粘性是通过onStartCommand()方法的返回值确定的,可用的值有:
—–Service.START_REDELIVER_INTENT -> 粘性的,且在自动重启时,会重新给Service发送Intent对象。
—–Service.START_STICKY -> 粘性的
—–Service.START_NOT_STICKY -> 非粘性的
—–Service.START_STICKY_COMPATIBILITY -> 粘性的,并且兼容的
当需要Service是非粘性的,取值Service.START_NOT_STICKY;当需要Service是粘性的,并且还需要获取Intent对象时,取值Service.START_REDELIVER_INTENT;否则,只是需要粘性的,不需要Intent时,取值super.onStartCommand()默认值。
【2】Service是单例的,在程序中一个Service类只会存在一个对象
【3】Service是没有界面的,适合于在后台进行耗时操作,但要注意Service仍然是运行在主线程中的,故耗时的操作还是需要开启子线程来进行。

ContentProvider

1.作用 中文意思是内容提供者,ContentProvider可以将应用程序自身的数据对外(对其它应用程序)共享,使得其它应用可以对自身的数据进行增、删、改、查操作。
Android系统使用了许多ContentProvider,将系统中的绝大部分常规数据进行对外共享,例如:联系人资料、通话记录、短信、相册、歌曲、视频、日历等等,一般这些数据都存放于一个个的数据库中。
【实现】
由于ContentProvider可提供增、删、改、查这些操作,通常结合SQLite使用。
ContentResolver是读取由ContentProvider共享的数据的工具。通过Context类定义的getContentResolver()方法,可以获取ContentResolver对象。如果您不打算与其他应用共享数据,则无需开发自己的提供程序。

2.访问Content Provider

Content Provider以一个或多个表(与在关系型数据库中找到的表类似)的形式将数据呈现给外部应用。 行表示提供程序收集的某种数据类型的实例,行中的每个列表示为实例收集的每条数据。

应用从具有 ContentResolver对象的Content Provider访问数据。 此对象具有调用提供程序对象(ContentProvider 的某个具体子类的实例)中同名方法的方法。 ContentResolver 方法可提供持续存储的基本“CRUD”(创建、检索、更新和删除)功能。

客户端应用进程中的 ContentResolver 对象和拥有提供程序的应用中的 ContentProvider 对象可自动处理跨进程通信。 ContentProvider 还可充当其数据存储区和表格形式的数据外部显示之间的抽象层。

注:要访问提供程序,您的应用通常需要在其清单文件中请求特定权限。 内容提供程序权限部分详细介绍了此内容。

例如,要从用户字典提供程序中获取字词及其语言区域的列表,则需调用 ContentResolver.query()。 query() 方法会调用用户字典提供程序所定义的 ContentProvider.query() 方法。 以下代码行显示了 ContentResolver.query() 调用:

// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI of the words table,URI映射的数据表名
mProjection, // The columns to return for each row,我们所要我的选择查询的表的列名,字符串数组
mSelectionClause // Selection criteria,查询条件相当于SQL中的where a =? and b = ?
mSelectionArgs, // Selection criteria,字符串数组,替代上面条件中的?占位符
mSortOrder); // The sort order for the returned rows,排列的顺序相当于SQL中的order(字段)

3.URI 资源访问格式
内容 URI 是用于在Content Provider中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其授权)和一个指向表的名称(路径)。 当您调用客户端方法来访问提供程序中的表时,该表的内容 URI 将是其参数之一。

在前面的代码行中,常量 CONTENT_URI 包含用户字典的“字词”表的内容 URI。 ContentResolver 对象会分析出 URI 的授权,并通过将该授权与已知提供程序的系统表进行比较,来“解析”提供程序。 然后, ContentResolver 可以将查询参数分派给正确的提供程序。

ContentProvider 使用内容 URI 的路径部分来选择要访问的表。 提供程序通常会为其公开的每个表显示一条路径。

在前面的代码行中,“字词”表的完整 URI 是:

content://user_dictionary/words

其中,user_dictionary 是提供程序的授权,需要在清单文件中注册words 是表的路径; content://(架构)始终显示,并将此标识为内容 URI

许多提供程序都允许您通过将 ID 值追加到 URI 末尾来访问表中的单个行。 例如,要从用户字典中检索 _ID 为 4 的行,则可使用此内容 URI:

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

在检索到一组行后想要更新或删除其中某一行时通常会用到 ID 值。

注:Uri 和 Uri.Builder 类包含根据字符串构建格式规范的 URI 对象的便利方法。 ContentUris 包含一些可以将 ID 值轻松追加到 URI 后的方法。 前面的代码段就是使用 withAppendedId() 将 ID 追加到 UserDictionary 内容 URI 后。

4.显示数据
ContentResolver.query() 方法始终会返回符合以下条件的 Cursor:包含查询的表为匹配查询选择条件的行指定的列, Cursor 对象为其包含的行和列提供随机读取访问权限。 通过使用 Cursor 方法,您可以循环访问结果中的行、确定每个列的数据类型、从列中获取数据,并检查结果的其他属性。 某些 Cursor 实现会在提供程序的数据发生更改时自动更新对象或在 Cursor 更改时触发观察程序对象中的方法。

注:提供程序可能会根据发出查询的对象的性质来限制对列的访问。 例如,联系人提供程序会限定只有同步适配器才能访问某些列,因此不会将它们返回至 Activity 或服务。

如果没有与选择条件匹配的行,则提供程序会返回 Cursor.getCount() 为 0(空游标)的 Cursor 对象。

如果出现内部错误,查询结果将取决于具体的提供程序。它可能会选择返回 null,或引发 Exception。

由于 Cursor 是行“列表”,因此显示 Cursor 内容的较好方式是通过 SimpleCursorAdapter 将其与 ListView 关联。

以下代码段会创建一个包含由查询检索到的 Cursor 的 SimpleCursorAdapter 对象,并将此对象设置为 ListView 的适配器:

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
UserDictionary.Words.WORD, // Contract class constant containing the word column name
UserDictionary.Words.LOCALE // Contract class constant containing the locale column name
};

// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
getApplicationContext(), // The application's Context object
R.layout.wordlistrow, // A layout in XML for one row in the ListView
mCursor, // The result from the query
mWordListColumns, // A string array of column names in the cursor
mWordListItems, // An integer array of view IDs in the row layout
0); // Flags (usually none are needed)

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);
注:要通过 Cursor 支持 ListView,游标必需包含名为 _ID 的列。 正因如此,前文显示的查询会为“字词”表检索 _ID 列,即使 ListView 未显示该列。 此限制也解释了为什么大多数提供程序的每个表都具有 _ID 列。

获取某个列的值

// 得到words表中WORD的字段标签,也即是上面查询时列名的位置mWordListColumns的下标,这里应该是第一个
int index =mCursor.getColumnIndex(UserDictionary.Words.WORD);
if (mCursor != null) {
while (mCursor.moveToNext()) {
// 得到该下标列名的值.
newWord = mCursor.getString(index);
}
} else {

}

5.创建Content Provider
实现 ContentProvider 类,实现它的抽象方法。

query()
从您的提供程序检索数据。使用参数选择要查询的表、要返回的行和列以及结果的排序顺序。 将数据作为 Cursor 对象返回。
insert()
在您的提供程序中插入一个新行。使用参数选择目标表并获取要使用的列值。 返回新插入行的内容 URI。
update()
更新您提供程序中的现有行。使用参数选择要更新的表和行,并获取更新后的列值。 返回已更新的行数。
delete()
从您的提供程序中删除行。使用参数选择要删除的表和行。 返回已删除的行数。
getType()
返回内容 URI 对应的 MIME 类型。实现内容提供程序 MIME 类型部分对此方法做了更详尽的描述。
onCreate()
初始化您的提供程序。Android 系统会在创建您的提供程序后立即调用此方法。 请注意,ContentResolver 对象尝试访问您的提供程序时,系统才会创建它。

设计内容 URI

内容 URI 是用于在提供程序中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其授权)和一个指向表或文件的名称(路径)。 可选 ID 部分指向表中的单个行。 ContentProvider 的每一个数据访问方法都将内容 URI 作为参数;您可以利用这一点确定要访问的表、行或文件。

设计授权
提供程序通常具有单一授权,该授权充当其 Android 内部名称。为避免与其他提供程序发生冲突,您应该使用互联网网域所有权(反向)作为提供程序授权的基础。 由于此建议也适用于 Android 软件包名称,因此您可以将提供程序授权定义为包含该提供程序的软件包名称的扩展名。 例如,如果您的 Android 软件包名称为

 com.example.<appname>

就应使用com.example.<appname>.provider 授权。

设计路径结构
开发者通常通过追加指向单个表的路径来根据权限创建内容 URI。 例如,如果您有两个表:table1 和 table2,则可以通过合并上一示例中的权限来生成 内容 URI com.example..provider/table1 和 com.example..provider/table2。路径并不限定于单个段,也无需为每一级路径都创建一个表。

处理内容 URI ID
按照惯例,提供程序通过接受末尾具有行所对应 ID 值的内容 URI 来提供对表中单个行的访问。 同样按照惯例,提供程序会将该 ID 值与表的 _ID 列进行匹配,并对匹配的行执行请求的访问。

这一惯例为访问提供程序的应用的常见设计模式提供了便利。应用会对提供程序执行查询,并使用 CursorAdapter 以 ListView 显示生成的 Cursor。 定义 CursorAdapter 的条件是, Cursor 中的其中一个列必须是 _ID

用户随后从 UI 上显示的行中选取其中一行,以查看或修改数据。 应用会从支持 ListView 的 Cursor 中获取对应行,获取该行的 _ID 值,将其追加到内容 URI,然后向提供程序发送访问请求。 然后,提供程序便可对用户选取的特定行执行查询或修改。

内容 URI 模式
为帮助您选择对传入的内容 URI 执行的操作,提供程序 API 加入了实用类 UriMatcher,它会将内容 URI“模式”映射到整型值。 您可以在一个 switch 语句中使用这些整型值,为匹配特定模式的一个或多个内容 URI 选择所需操作。

内容 URI 模式使用通配符匹配内容 URI:

*:匹配由任意长度的任何有效字符组成的字符串
#:匹配由任意长度的数字字符组成的字符串

以设计和编码内容 URI 处理为例,假设一个具有授权
com.example.app.provider 的提供程序能识别以下指向表的内容 URI:

content://com.example.app.provider/table1:一个名为 table1 的表
content://com.example.app.provider/table2/dataset1:一个名为 dataset1 的表
content://com.example.app.provider/table2/dataset2:一个名为 dataset2 的表
content://com.example.app.provider/table3:一个名为 table3 的表
提供程序也能识别追加了行 ID 的内容 URI,例如,content://com.example.app.provider/table3/1 对应由 table3 中 1 标识的行的内容 URI。

可以使用以下内容 URI 模式:

content://com.example.app.provider/*
匹配提供程序中的任何内容 URI。

content://com.example.app.provider/table2/*:
匹配表 dataset1 和表 dataset2 的内容 URI,但不匹配 table1 或 table3 的内容 URI。

content://com.example.app.provider/table3/#:匹配 table3 中单个行的内容 URI,如 content://com.example.app.provider/table3/6 对应由 6 标识的行的内容 URI。

在清单文件中注册实现content provider的类

与 Activity 和 Service 组件类似,必须使用 provider 元素在清单文件中为其应用定义 ContentProvider 的子类。 Android 系统会从该元素获取以下信息:

授权 (android:authorities)
用于在系统内标识整个提供程序的符号名称,也即是上面所说的URI包含路径名表名的字符串。
提供程序类名 ( android:name )
实现 ContentProvider 的类。实现 ContentProvider 类中对此类做了更详尽的描述。

BraodCast Receiver

1.概述
广播接收器,顾名思义这是用于接收应用发送的广播的系统组件。广播是一种1对多的通信方式,即存在1个发送方,若干个接收方。在Android系统,把具有这样的数据的传递方式的机制称之为“广播”。Android系统会在特定的情景下发出各种广播,例如开机、锁屏了、电量不足了、正在充电了、呼出电话了、被呼叫了……
广播是一种跨进程的、“全设备之内”的通信方式。

2.发送广播
调用Context对象的sendBroadcast(Intent)即可发送广播,在参数Intent对象中,应该调用setAction()方法配置广播的“频道号”,即是相当于收音机要接收某个电台的频段,只有注册了相同的Action的广播接收者才可以接收到该广播。

3.接收广播
自定义类,继承自android.content.BroadcastReceiver后需要注册,注册时,使用IntentFilter配置与发送方相同的Action,重写onReceiver()方法实现对广播的处理。

public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
Toast.makeText(context, log, Toast.LENGTH_LONG).show();
}
}

4.注册广播接收器
广播接收者的注册可以区分为静态注册和动态注册:

  • 静态注册:在清单文件AndroidManifest.xml中,在application节点下使用receiver节点进行注册。这种方式注册的广播接收者必须是一个单独的只实现BroadcastReceiver的类,不能是内部类。且这样的广播接收者是常驻型的,即从APP安装到手机上的那一刻即开始处于接收广播状态,且直至该APP被从手机移除。
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
  • 动态注册:在程序中调用Context对象的registerReceiver(BroadcastReceiver, IntentFilter)方法进行注册。这种方式可以注册以内部类的形式存在的广播接收者,且这种方式的广播接收者仅当注册后才开始接收广播,并且在调用了Context对象的unregisterReceiver(BroadcastReceiver)方法后就会停止接收广播。
BroadcastReceiver br = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);

5.无序广播与有序广播
1. 普通的广播即为无序广播,谁都可以接收,并不会相互打扰。
2. 有序广播:调用sendOrderedBroadcast(Intent, String permission)方法发送的广播,各广播接收者在接收广播时,会存在一定的先后顺序,即某接收者会先收到广播,其他接收者后收到广播,广播会在各接收者之间按照一定的先后顺序进行传递。在广播的传递过程中,先接收到广播的接收者可以对广播进行拦截或篡改。

6.有序广播的接收者们的优先级
有序广播的接收者们的优先级用于确定接收的先后顺序,优先级越高的接收者,将更优先接收到广播,反之,则更靠后接收到广播。
1. 注册广播时,在广播对应的IntentFilter中的priority属性直接决定优先级,该属性值为int类型的数值,取值越大,则优先级越高!
2. 如果存在多个广播接收者配置的priority属性值相同,则动态注册的广播接收者的优先级高于静态注册的广播接收者。
3. 如果根据以上2条规则都无法确定优先级,则根据注册的先后顺序确定各接收者们的优先级。

7.有序广播的拦截或篡改
1. 【拦截】在广播接收者中,使用abortBroadcast()方法,可以终止有序广播向后继续传递,即后续的接收者们将无法接收到该广播。注意:该方法只能在接收有序广播时调用!
2. 【篡改】在广播接收者中,调用setResult()方法,可以向广播中添加数据,并在后续的接收者中,可以通过getResult()获取这些数据,同时,后续的接收者也可以再次调用setResult()方法重新向广播中写入数据,即覆盖原有的数据,以实现篡改。