Activity
定义与背景
-
Activity是一个应用程序组件,提供一个屏幕,用户可以用来交互以完成某项任务。它就像是一个舞台,在这个舞台上,用户可以看到各种界面元素,如按钮、文本框、列表等,并与它们进行交互。例如,在一个购物应用中,商品列表页、商品详情页、购物车页等都是Activity,用户可以在这些页面上查看商品信息、添加商品到购物车、进行结算等操作。
Activity 的启动模式
Activity的启动模式是指决定生成新的Activity实例时是否重用已存在的Activity实例,以及是否和其他Activity实例公用一个任务栈(task)的规则。
standard(标准模式)
想象你有一个任务栈就像一个放东西的盒子。在这种模式下,每次你想要打开(激活)一个Activity,就像往这个盒子里放一个新的东西(创建一个新的Activity实例)。不管这个东西(Activity)之前有没有在盒子里,每次都放一个新的进去。
例如,你打开一个新闻应用,每次点击一篇新闻进入新闻详情页,都会创建一个新的新闻详情Activity实例并放入任务栈。
singleTop(单一顶部模式)
还是把任务栈当作盒子。如果某个Activity(我们叫它A)在盒子的最上面(也就是任务栈的栈顶),然后又要再次打开这个A,那就不需要再创建一个新的A了,就像盒子最上面已经有这个东西了,就不用再放一个新的了。但是如果A不在盒子最上面,或者盒子里根本没有A,那就要创建一个新的A并放入盒子(任务栈)。
比如,在一个搜索结果页面,如果这个页面在栈顶,再次搜索进入这个页面就不用创建新的实例。
singleTask(单一任务模式)
把任务栈当作一个有顺序的架子。如果要打开的Activity(假设是B)已经在这个架子(任务栈)上了,那就不需要创建新的B了。而且要把B放到架子的最上面(任务栈的栈顶),同时把B上面的其他东西(Activity实例)都拿下来(出栈)。就好像你要找一个东西,这个东西在架子中间,你把它拿出来放到最上面,同时把它上面的东西都移走。
例如,在一个电商应用中,商品列表页、商品详情页、购物车页,如果商品详情页是singleTask
模式,当从购物车页再进入商品详情页时,商品详情页会被移到栈顶,中间可能有的其他页面(如果有的话)会出栈。
那在singleTask(单一任务模式)模式下,出栈的activity重新被调用是不是要从头开始走生命周期?
在
singleTask
模式下,当重新启用出栈后的Activity时,由于处于非栈顶的Activity都被完全覆盖了,所以生命周期不仅仅会运行onPause
还会到onStop
,在重新启用时,会先调用onRestart
,再执行onStart - onResume
的顺序,而不是从头开始走生命周期。当在
singleTask
模式下,Activity B上面的其他Activity出栈后,这些Activity就从任务栈中移除了。对于出栈Activity的生命周期是怎么样的?
首先会执行
onPause
方法,因为即将失去焦点。接着执行
onStop
方法,因为已经不可见了。如果系统内存紧张等情况,这些出栈的Activity实例可能会被销毁,此时会执行
onDestroy
方法。如果没有被销毁,当再次需要显示这些Activity时(例如通过回退操作等),会从onRestart
方法开始,接着执行onStart
和onResume
方法来重新显示。
singleInstance(单一实例模式)
这就像给某个特殊的Activity(假设是C)单独建了一个小盒子(独立的任务栈),这个小盒子里只有C这一个东西(Activity实例),不允许有其他的Activity在这个小盒子里。当需要打开C的时候,就直接找到这个单独的小盒子里的C,而且整个应用里只有这一个C的实例。
比如,在一个音乐播放应用中,音乐播放的控制界面可能是singleInstance
模式,它独立运行在自己的小任务栈中,和其他页面分开。
Activity的生命周期
Activity的生命周期就像是一个人的成长过程,有不同的阶段。
启动阶段(onCreate)
-
这就像是一个人出生的时候。当Activity被创建时,会调用
onCreate
方法。在这个阶段,你可以做一些初始化的工作,比如设置布局(就像给人穿上衣服),初始化一些变量等。例如:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 这里可以初始化变量,比如定义一个计数器并初始化为0 int count = 0; }
可见阶段(onStart)
-
这相当于人已经出现在大家面前了,但还没有完全准备好互动。当Activity变得可见时,会调用
onStart
方法。此时,Activity已经在屏幕上显示了一部分,但可能还没有获取到焦点(就像人在舞台上,但还没有开始表演)。
可交互阶段(onResume)
-
这就像人已经完全准备好,可以和外界互动了。当Activity获取到焦点,可以和用户进行交互时,会调用
onResume
方法。例如,用户可以在这个时候点击按钮、输入文字等。
暂停阶段(onPause)
-
这有点像人突然被打断了一下。当有另一个Activity部分覆盖当前Activity时(比如弹出一个透明的对话框),当前Activity会进入
onPause
状态。在这个阶段,Activity仍然是可见的,但不能完全和用户交互了。例如,你正在玩一个游戏(Activity),突然来了一个电话通知(另一个Activity部分覆盖),游戏就暂停了。不过在这个阶段,你可以保存一些临时的数据,比如游戏的当前进度。
停止阶段(onStop)
-
这就像人离开了舞台,看不到了。当Activity完全被另一个Activity覆盖时,会调用
onStop
方法。此时Activity虽然还在内存里(就像人虽然不在舞台上了,但还在后台准备着),但是不再与windowmanager
联接了。如果系统内存不足,这个停止的Activity可能会被销毁。
重新启动阶段(onRestart)
-
这就像人又重新回到舞台准备再次表演。当一个已经停止的Activity再次被启动时,会先调用
onRestart
方法,然后再走onStart
和onResume
的流程。
销毁阶段(onDestroy)
-
这就像人彻底消失了。当Activity被销毁时,会调用
onDestroy
方法。可能是因为用户按了返回键,或者系统为了释放内存而销毁了这个Activity。在这个阶段,你可以释放一些资源,比如关闭数据库连接、停止正在播放的音乐等。
Activity的跳转方式
根据搜索结果可知有两种跳转方式,并没有提及三种跳转方式,以下是这两种跳转方式的讲解:
无返回数据的跳转
startActivity无返回数据的跳转
就像是你从一个房间(Activity)走到另一个房间(另一个Activity),只是单纯地过去,不需要带任何东西回来。
例如,从主页面(FirstActivity)跳转到一个设置页面(SecondActivity),只是进入设置页面进行一些设置操作,不需要把设置页面的任何数据返回给主页面。
在代码中,就像这样:
public void onClick(View arg0) { //跳转到SecondActivity并传值 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivity(intent); }
有返回数据的跳转
startActivityForResult(intent, int requestCode)有返回数据的跳转
-
这就好比你从一个房间(FirstActivity)去另一个房间(SecondActivity)拿东西,然后再回来。
-
在SecondActivity中通过
setResult(RESULT_CODE, intent);
设置返回的数据。例如,从主页面跳转到一个选择联系人的页面,在选择联系人页面选择好联系人后,要把选择的联系人信息返回给主页面。 -
在FirstActivity中重写
onActivityResult()
方法就可以拿到返回的数据。代码大概是这样的: -
在FirstActivity中跳转:
public void onClick(View arg0) { //跳转到SecondActivity并传值 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivityForResult(intent, 1); //这里的1就是requestCode,用于标识请求 }
-
在SecondActivity中设置返回数据:
Intent resultIntent = new Intent(); resultIntent.putExtra("selected_contact", "张三"); setResult(RESULT_OK, resultIntent); finish();
-
在FirstActivity中接收返回数据:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1 && resultCode == RESULT_OK) { String selectedContact = data.getStringExtra("selected_contact"); // 这里就得到了从SecondActivity返回的联系人信息 } }
Service
定义与背景
由于手机屏幕的限制,通常情况下在同一时刻仅有一个应用程序处于激活状态,并能够显示在手机屏幕上,因此,应用程序需要一种机制,在没有用户界面的情况下,能够长时间在后台运行,实现应用程序的特定功能,并能够处理事件或更新数据。Android 系统提供了 Service 服务组件,它不直接与用户进行交互,却能够长期在后台运行。有很多情况需要使用 Service,典型的例子就是:MP3 播放器。
Service 非常适用于无需用户干预,且需要长期运行的后台功能。Service 没有用户界面,有利于降低系统资源。而且 Service 比 Activity 具有更高的优先级,只有在系统资源极度匮乏的情况下,android 系统才可能清理掉一部分 service 来保证系统的运行,而这种情况却又轻易不会出现。即使 Service 被系统终止了,在系统资源恢复后 Service 也将自动恢复运行状态,因此可以认为 Service 是在系统中永久运行的组件。Service 除了实现后台服务功能,还可以用于进程间通信,解决两个不同 Activity 应用程序进程之间的调用和通信问题。
Service的使用
安卓的Service(服务)是一种可以在后台执行长时间运行操作而没有用户界面的应用组件。
-
功能示例
-
它可以处理网络事务,比如在后台持续下载文件,即使你切换到其他应用,下载也能继续进行。
-
能够播放音乐,当你在手机上打开音乐播放器开始播放音乐后,即使你退出音乐播放器界面去做其他事情,如查看邮件、浏览网页等,音乐依然可以通过Service在后台持续播放。
-
执行文件I/O操作,例如在后台对手机存储中的文件进行读写操作。
-
与内容提供程序交互,获取或更新手机中的联系人、短信等数据。
-
-
类型
-
启动状态(startService启动)
-
当应用组件(如Activity)通过调用
startService()
启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件(如Activity)已被销毁也不受影响,除非手动调用才能停止服务。已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,一个后台文件下载服务,启动后就持续下载文件,不需要给启动它的Activity反馈下载结果。
-
-
绑定服务(bindService启动)
-
提供了一个客户端 - 服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信(IPC)跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。比如,一个音乐播放服务,Activity可以绑定到这个服务上,向它发送播放、暂停、切换歌曲等请求,并且能获取当前播放歌曲的相关信息(如歌名、时长等)。
-
-
-
在清单文件中的声明
-
无论Service是启动状态还是绑定状态,都是通过继承Service基类自定义而来,并且都需要在
AndroidManifest.xml
中声明。这就像是给安卓系统一个通知,告诉它这个应用有这样一个服务组件。
-
-
使用场景
-
startService:适用于需要在后台长时间运行的任务,例如下载文件、播放音乐等。
-
bindService:适用于需要与服务频繁交互的任务,例如获取实时数据、控制服务行为等。
-
-
交互场景
-
startService:不提供直接与组件交互的接口,主要用于执行后台任务。
-
bindService:提供与组件交互的接口,组件可以调用服务中的方法进行通信。
-
Service的生命周期
-
启动状态(StartedService)的生命周期
-
onCreate()
-
这个方法在服务第一次被创建时调用,就像服务的“出生”阶段。在这里可以进行一些一次性的初始化操作,例如初始化一些变量、加载资源等。例如,如果服务需要连接数据库,就可以在
onCreate
方法中建立数据库连接。
-
-
onStartCommand()
-
在程序中调用
context.startService()
时会触发这个方法(在2.0以前版本中,使用onStart()
回调方法)。如果服务还没有运行,那么Android系统会先调用onCreate()
然后调用onStartCommand()
;如果服务已经运行,则只调用onStartCommand()
。所以一个服务的onStartCommand
方法可能会重复调用多次。这个方法是服务开始运行的标志,在这个方法里可以处理服务的主要业务逻辑,比如开始一个耗时的操作,像在后台持续下载文件或者播放音乐等。
-
-
onDestroy()
-
当在程序中调用
context.stopService()
时会触发执行这个回调方法,这是服务的“死亡”阶段。在这个方法里可以进行一些清理操作,例如释放资源,如果之前在onCreate
中建立了数据库连接,那么可以在onDestroy
中关闭数据库连接。如果是调用者自己直接退出而没有调用stopService()
的话,Service会一直在后台运行。
-
-
-
绑定服务(bindService)的生命周期
-
onCreate()
-
同样是在服务第一次创建时调用,进行初始化操作。
-
-
onBind()
-
当组件(如Activity)调用
bindService()
方法绑定到这个服务时,会调用onBind()
方法。这个方法返回一个IBinder
对象,它是服务与绑定组件之间通信的接口。例如,一个音乐播放服务,当Activity绑定到这个服务时,onBind()
方法返回的IBinder
对象可以让Activity向服务发送播放、暂停等指令。
-
-
onUnbind()
-
当所有绑定到服务的组件都调用
unbindService()
方法解除绑定后,会调用onUnbind()
方法。在这个方法里可以进行一些与解除绑定相关的操作,比如清理与绑定组件之间的通信相关的资源。
-
-
onDestroy()
-
当没有任何组件绑定到服务,并且服务也没有被启动(
startService
)时,服务会被销毁,此时调用onDestroy()
方法进行最后的清理工作。
-
-
-
总结
-
startService:服务由 startService 启动后会一直运行,直到调用 stopSelf() 或 stopService()。它不依赖于启动它的组件的生命周期。
-
bindService:服务由 bindService 启动后会在所有绑定的组件解除绑定时停止。它的生命周期依赖于绑定的组件。
ContentProvider
定义与背景
-
安卓中的ContentProvider(内容提供者)就像是一个数据共享的“桥梁”或者“中介”。它是安卓中的四大组件之一。
-
想象你有一个房子(一个安卓应用),房子里有很多东西(数据,比如联系人信息、短信内容、图片等)。ContentProvider的作用就是把房子里的这些东西(数据)拿出来,分享给其他房子(其他安卓应用)使用。
主要用途 - 数据共享
-
例如,手机里有一个联系人管理应用,这个应用里存储了联系人的姓名、电话、邮箱等信息。如果有另一个应用,比如一个短信群发应用,它想要获取联系人的电话号码来发送短信,就可以通过ContentProvider来获取联系人应用中的电话号码数据。
-
再比如,相册应用存储了很多图片,而一个图片编辑应用想要获取相册里的图片进行编辑,也是通过ContentProvider来实现数据的获取。
采用的是一种叫做 匿名共享内存的方式进行数据传递,在不同的进程中只需要传递一个文件描述符就可以。
通过下图对 content provider 有个比较直观的了解:
ContentProvider的类型
-
系统的ContentProvider
-
就像手机系统自带的一些数据,如联系人、短信、图片等都有对应的系统ContentProvider。这些系统级别的数据可以被其他应用访问(当然,需要相应的权限)。例如,当你安装一个新的社交应用时,它可能会请求读取你的联系人信息,这就是通过系统的联系人ContentProvider来实现的。
-
-
自定义的ContentProvider
-
除了系统提供的,开发者也可以创建自己的ContentProvider。假设你开发了一个笔记应用,这个应用里存储了用户的笔记内容。如果你想让其他应用也能使用这些笔记内容(比如一个统计笔记字数的小工具应用),就可以创建一个自定义的ContentProvider来共享笔记数据。
-
ContentProvider的使用
-
其他应用要想使用ContentProvider共享的数据,需要通过ContentResolver这个对象。ContentResolver就像是一个“办事员”,它知道如何与ContentProvider打交道。
-
例如,如果要查询联系人的电话号码,首先要获取ContentResolver对象(在安卓的Activity或者其他组件中可以很容易获取到),然后使用ContentResolver的
query
方法来查询ContentProvider中的数据。就像你要找联系人的电话号码,先找到办事员(ContentResolver),然后让办事员去联系人数据的“仓库”(ContentProvider)里查询电话号码。 -
同样,如果要插入、更新或者删除ContentProvider中的数据,也可以通过ContentResolver的
insert
、update
、delete
方法来操作。
ContentProvider共享原理
在Android中,ContentProvider使用URI(统一资源标识符)来标识要操作的数据资源。就像每个地方都有一个地址一样,URI就是ContentProvider中数据的“地址”。
在 Android 中 URI 的格式如下:
URI =
<schema>://<authority>/<path>/<id>
例如:content://com.jeanboy.provider/User/1
-
主题(schema)
ContentProvider 的 URI 前缀,表示是一个 Android 内容 URI,说明由 ContentProvider 控制数据,该部分是固定形式,不可更改。
-
授权信息(authority)
URI 的授权部分,是唯一标识符,用来定位 ContentProvider。格式一般是自定义 ContentProvider 类的完全限定名称,注册时需要用到。如:com.jeanboy.provider.TestProvider。
比如,对于系统的联系人ContentProvider,它可能有一个特定的授权名称,像contacts
之类的(实际情况可能更复杂)。不同的ContentProvider有不同的授权名称,就像不同的公司有不同的名字一样。当你想要访问某个ContentProvider的数据时,通过这个授权名称就能找到对应的ContentProvider。
-
表名(path)
路径片段,一般用表的名字,指向数据库中的某个表名。
例如,在联系人ContentProvider中,<path>
可能是contacts/phones
表示要访问联系人的电话号码数据集合。这就像是在一个公司(ContentProvider)里,<path>
指向了具体的部门(数据类型或集合)。
-
记录(id)
指向特定的记录,如表中的某个记录(若无指定,则返回全部记录)。
比如在联系人的电话号码数据集合中,如果<id>
为123
,可能表示联系人中某个特定联系人(其电话号码记录对应的id为123)的电话号码信息。这就像是在一个部门(由<path>
指定)里,<id>
指向了具体的一个员工(具体的数据项)。
BroadcastReciver
定义与背景
-
Broadcast Receiver(广播接收器)就像是一个收音机,它可以接收来自系统或者应用发出的广播消息。在安卓系统中,很多事件都会以广播的形式发送出去,例如电池电量变化、网络连接状态改变、系统启动完成等。Broadcast Receiver的任务就是监听这些广播,当接收到自己感兴趣的广播时,就可以做出相应的反应。
Broadcast Receiver的注册
-
静态注册 ——在 AndroidManifest.xml 里通过 < receive 标签声明
-
这种方式是在AndroidManifest.xml 文件中进行注册。就像在房子(安卓应用)的蓝图(清单文件)里提前设置好一个收音机(Broadcast Receiver),告诉系统这个收音机要接收哪些广播。例如,要接收电池电量变化的广播,可以在清单文件中这样注册:
<receiver android:name=".MyBroadcastReceiver"> <intent - filter> <action android:name="android.intent.action.BATTERY_CHANGED"/> </intent - filter> </receiver>
-
这里定义了一个名为
MyBroadcastReceiver
的广播接收器,并且通过<intent - filter>
指定它要接收电池电量变化(android.intent.action.BATTERY_CHANGED
)这个广播。
-
-
动态注册 ——在代码中调用 Context.registerReceiver() 方法
-
动态注册是在代码中进行注册的。这就好比你在房子(应用)里临时放一个收音机(Broadcast Receiver)来接收广播。例如,在一个Activity中:
Java复制public class MainActivity extends Activity { private MyBroadcastReceiver myBroadcastReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myBroadcastReceiver = new MyBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("android.intent.action.BATTERY_CHANGED"); registerReceiver(myBroadcastReceiver, intentFilter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(myBroadcastReceiver); } }
-
在
onCreate
方法中创建了MyBroadcastReceiver
并注册它来接收电池电量变化的广播,然后在onDestroy
方法中要记得取消注册(unregisterReceiver
),就像把收音机拿走一样。
-
Broadcast Receiver的类型
-
普通广播(Normal Broadcast)
-
普通广播是完全异步的广播。就像在一个大广场上喊一句话,听到的人(广播接收器)可以同时做出反应,没有先后顺序。发送普通广播的速度比较快,因为它不需要等待每个接收器都处理完。例如,一个应用可以发送一个普通广播来通知其他应用某个事件发生了,其他应用中的广播接收器如果注册了接收这个广播,就可以各自独立地处理这个事件。
-
-
有序广播(Ordered Broadcast)
-
有序广播是按照一定的优先级顺序传递的广播。这就好比在一个有等级的组织里传达一个消息,先传给级别高的人(优先级高的广播接收器),然后再依次往下传。优先级高的广播接收器可以对广播进行处理,甚至可以终止广播,不让后面的接收器接收到。例如,系统发送一个有序广播来处理网络连接状态改变的情况,高优先级的安全相关的应用可以先处理这个广播,可能会根据网络连接情况调整安全策略,然后再让其他应用的广播接收器处理。
-
Broadcast Receiver的示例
-
当网络连接状态从断开变为连接时,系统会发送一个网络连接状态改变的广播。一个下载应用中的Broadcast Receiver接收到这个广播后,就可以自动开始之前暂停的下载任务。
-
当电池电量低时,系统发送电池电量低的广播,手机中的一些电量管理应用中的Broadcast Receiver接收到这个广播后,可以采取一些节能措施,如关闭一些后台服务或者降低屏幕亮度等。