
Activity
1. 生命周期

正常情况下:
- 启动 Activity:系统先调用
onCreate(),然后调用onstart(),最后调用onResume(),Activity 就此进入运行状态。 - 退出 Activity:系统先调用
onPause(),然后调用onStop(),最后调用onDestroy(),Activity 就此销毁。 - 当前设备锁屏或者点击 Home 键使程序进入后台,依次执行
onPause()、onStop(), 重新回到 Activity,依次执行onRestart()、onStart()、onResume()(Activity 不被回收的情况下)。 - 当前 Activity 被 Dialog 主题的 Activity 覆盖时,执行
onPause(),回到前台执行onResume()。 - 当 Activity 不在前台或者不可见时被系统回收后,再次回到此 Activity ,系统会依次执行
onCreate()、onStart()、onResume()。 - 启动一个新 Activity,旧 Activity 会先执行
onPause(),然后新 Activity 再启动。 - 按是否可见分类:
onStart()、onStop()。 - 按是否前台分类:
onResume()、onPause。
注意:当 Activity 弹出对话框时,并不会回调onPause,但是会回调onStop()。
异常情况下:
当系统内存不足,或者系统配置发生改变(如旋转方向),Activity 会被杀死。
- 由于是异常情况下终止的,系统会调用
onSaveInstanceState()来保存当前 Activity 的状态,这个方法的调用时机是在onStop()之前,当 Activity 重新创建后,系统会把销毁时保存的 Bundle 对象作为参数传递给onCreate()和onRestoreInstanceState(),建议在onRestoreInstanceState()中做数据恢复,毕竟专门用来恢复实例状态的。另外,每个 View 本身都有onSaveInstanceState()和onRestoreInstanceState()方法,因此系统都会默认恢复 View 的基本状态。 - 防止重新创建 Activity:在 AndroidManifest.xml 中指定
android:configChanges="orientation",似乎有些设备需要多指定一个参数,即android:configChanges:="orientaion|screenSize"。
2. 启动模式
总共 4 种启动模式:Standard,SingleTop,SingleTask,SingleInstance。
- Standard:默认的启动模式,在这种模式下,Activity 会进入启动它的 Activity 所在的任务栈。
- SingleTop:如果新 Activity 位于任务栈的栈顶时,Activity 不会被创建,并且它的
onNewIntent()方法会被回调,其余生命周期方法均不会回调。 - SingleTask:如果 Activity 在一个任务栈中存在,那么多次启动此 Activity 都不会创建新实例,但系统会回调
onNewIntent()。此外,位于此 Activity 之上的所有 Activity 均会出栈,此时 Activity 位于栈顶。 - SingleInstance:这种模式下的 Activity 只能单独存在于一个任务栈中,由于栈内复用特性,此后的请求均不会创建新的实例。
注意:默认情况下,所有 Activity 所需的任务栈的名字为应用的包名,可以在 AndroidManifest.xml 中通过android:taskAffinity=""来指定任务栈。
Service
被启动的 Service 默认是在主线程下工作的,因此如果需要执行耗时操作,应当另开一个子线程来执行,以免阻塞主线程导致出现 ANR(Application Not Response)。任何 Activity 都可以控制同一个 Service,并且系统中也只会存在一个 Service 实例。
启动模式
1. startService()
- 普通模式:在这种模式下启动 Service 后,即使 Activity 销毁后,Service 也会在后台继续执行任务,直到在 Activity 中手动调用
stopService()或者在在 Service 类中调用stopSelf(),服务才会停止,Activity 无法与 Service 进行通信。 onCreate中可以做一些初始化,onStartCommand()中放置执行任务的代码,onDestroy()中进行资源的释放。- 在这种方式下启动 Service,生命周期是
onCreate()->onStartCommand()->onDestroy(),当 Service 已经被启动后,无论执行多少次startServvice(),都不会走onCreate(),而是会调用onStartCommand()。
2. bindService()
- 绑定模式:在这种模式下启动 Service,当 Activity 销毁后,Service 也会跟着销毁,不再使用 Service 时,调用
unbindService()停止。这种模式下可以进行 Activity 和 Service 的通信。 - 这这种方式下启动 Service,生命周期是
onCreate()->onBind()->onUnbind()->onDestroy(),onStartCommand()不会有调用机会。
注意:当startService()和bindService()一起被调用后,若想停止服务,必须同时调用stopService()和unbindService(),这样服务才会停止,顺序没有严格要求,但一定要同时调用。
示例:
- 创建一个 Service
public class MyService extends Service {
private static final String TAG = "MyService";
private IBinder mBinder;
@Override
public IBinder onBind(Intent intent) {
if (mBinder == null) {
mBinder = new MyBinder();
}
Log.d(TAG, "-----onBind()-----");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "-----onUnbind()-----");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "-----onCreate()-----");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "-----onDestroy()-----");
}
public class MyBinder extends Binder {
// 这里可以定义想要通信的方法,在 Activity 中可以通过 Binder 实例调用
public void print(String data) {
Log.d(TAG, "print: " + data);
}
}
}
- 在 AndroidManifest.xml 中注册
<!--android:enabled 表示是否启用,android:exported 表示是否向外界公开-->
<service
android:name=".MyService"
android:enabled="true"
android:exported="false" />
- 在 Activity 中定义 ServiceConnectioin 并进行绑定
public class MainActivity extends AppCompatActivity {
private ServiceConnection mConnection;
private MyService.MyBinder mBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mConnection = new MyConnection();
initView();
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
private void initView() {
Button bindService = findViewById(R.id.btn3);
Button unbindService = findViewById(R.id.btn4);
bindService.setOnClickListener(view -> {
bindService(new Intent(MainActivity.this, MyService.class), mConnection, BIND_AUTO_CREATE);
});
unbindService.setOnClickListener(view -> {
unbindService(mConnection);
});
}
private class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 转换为自己定义的 Binder
mBinder = (MyService.MyBinder) service;
// 调用自己定义的方法进行通信
mBinder.print("成功通信");
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 这个方法只有在出现异常的时候才会被系统调用
}
}
}
前台服务
服务几乎都是在后台运行的,系统优先级相对比较低,当系统出现内存不足时就容易被回收,如果希望服务可以一直保持运行状态,不会因为内存不足而被回收,这时就可以使用前台服务。与普通服务不同,前台服务会有一个正在运行的图标在通知栏里显示,类似通知。例如腾讯手机管家等通知,会在通知栏显示此时手机的内存状态等。
示例:在 Service 中通过startForeground()创建前台服务,然后在 Activity 中通过startService()或bindService()开启,通过stopService()或unbindService()关闭。
public class MyService extends Service {
private static final String TAG = "MyService";
private IBinder mBinder;
@Override
public void onCreate() {
super.onCreate();
createForegroundService();
}
private void createForegroundService() {
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification;
// 使用了建造者模式,将需要定义到的部分提前设置好
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "Channel_1")
.setContentTitle("前台服务")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi);
// 因为 Android 8.0 添加了 NotificationChannel(通知渠道)
// 因此需要适配,不然在 8.0 上会显示不了通知
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("Channel_1", "前台服务", NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
notification = builder.build();
} else {
notification = builder.build();
}
startForeground(1, notification);
}
......
}
IntentService
在前面就知道了,普通服务默认是运行在主线程中的,如果在服务里执行一些耗时操作就容易出现 ANR,当然也可以自己另开线程来执行,然后在合适的时机在 Service 内部调用stopSelf()来停止。但是这样稍显麻烦,为了可以简单地创建一个异步的、会自动停止的 Service,Android 提供了 IntentService 类,很好地解决了这个问题。
- 通过创建一个 Service 继承自 IntentService,并重写
onHandleIntent()方法即可,在这个方法中便可以处理耗时操作,并且当执行完毕后,Service 会自动停止。
示例:
public class MyIntentService extends IntentService {
public MyIntentService() {
// 必须调用父类有参构造
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Log.d("MyIntentService", "currentThread: " + Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "-----onDestroy()-----");
}
}
- 当执行完毕后,Service 自动停止,详情见 Logcat

远程服务
由于远程服务我并不熟悉,这里就先落下了,后面学习了再补充上去。
Broadcast
1. 广播
标准广播:
- 是一种完全异步执行的广播,所有的广播接收器之间几乎没有任何先后顺序。这种广播效率会比较高,但同时它是无法截断的。
sendBroadcast(intent)—— 表示发送标准广播
有序广播:
- 是一种同步执行的广播,广播发出后,同一时刻只会有一个广播接收器接收到这条广播,这种广播有先后顺序,优先级高的接收器可以先收到广播,并且前面的接收器可以截断正在传递的广播。
sendOrderedBroadcast(intent, null)—— 表示发送有序广播abortBroadcast()—— 表示截断接收到的广播
2. 接收器
注意: BroadcastReceiver 生命周期很短, 如果需要在
onReceiver()完成一些耗时操作,应该考虑在 Service 中开启一个新线程处理耗时操作,不应该在 BroadcastReceiver 中开启一个新的线程,因为 BroadcastReceiver 生命周期很短,在执行完 onReceiver 以后就结束,如果开启一个新的线程,可能出现 BroadcastRecevier 退出以后线程还在,而如果 BroadcastReceiver 所在的进程结束了,该线程就会被标记为一个空线程,根据 Android 的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程,而空线程无异是优先级最低的,这样就可能导致 BroadcastReceiver 启动的子线程不能执行完成。
动态注册(监听网络变化):
- 定义一个 Receiver 类继承自 BroadcastReceiver,并重写父类的
onReceive()方法,调用addAction()添加 action 值,当网络状态发生变化时,系统发出的正式一条值为android.net.conn.CONNECTIVITY_CHANGE的广播,想要监听什么广播便添加相应的 action 值。最后,动态注册的接受器一定要取消注册。
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}
@Override
protected void onDestroy(){
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
class NetworkChangeReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent){
ConnectivityManager manager = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if(networkInfo != null && networkInfo.isAvailable()){
Toast.makeText(context, "网络没毛病", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context, "网络不可用", Toast.LENGTH_SHORT).show();
}
}
}
}
-
在
onReceive()方法中,通过getSystemService()方法得到了 ConnectivityManager 的实例,这是一个系统服务类,专门用于管理网络连接。然后调用它的getActiveNetworkInfo()方法可以得到 NetworkInfo 的实例,接着调用 NetworkInfo 的isAvailable()方法即可判断当前是否有网络。 -
最后需要在 AndroidManifest.xml 中注册访问系统网络状态权限。
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
静态注册(监听系统开机):
- 使用 Android Studio 提供的快捷方式新建一个广播接收器,Exported 属性表示是否允许这个接收器接收本程序以外的广播,Enabled 属性表示是否启用这个接收器。
- AndroidManifest.xml 中 Android Studio 已经自动注册好了静态的广播接收器,由于需要监听开机广播,因此需要声明接收开机广播权限,并且 intent-filter 标签也需要添加相应的 action 值。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
静态注册与动态注册的区别
- 动态注册广播不是常驻型广播,也就是说广播跟随 Activity 的生命周期。需要在 Activity 结束前,移除广播接收器。 静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
- 当广播为有序广播时:
- 优先级高的先接收
- 同优先级的广播接收器,动态优先于静态
- 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。
- 当广播为普通广播时:
- 无视优先级,动态广播接收器优先于静态广播接收器
- 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。
3. 本地广播
本地广播用了一个 LocalBroadcastManager 来对广播进行管理,并提供了发送广播和注册广播接收器的方法。另外,本地广播是无法通过静态注册的方式来接收的
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent =
new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent);
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
}
@Override
protected void onDestroy(){
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent){
// 编写逻辑操作
}
}
}
ContentProvider
ContentProvider 是 Android 四大组件之一的内容提供器,它主要的作用就是将程序的内部的数据和外部进行共享,为数据提供外部访问接口,被访问的数据主要以数据库的形式存在,而且还可以选择共享哪一部分的数据。这样一来,对于程序当中的隐私数据可以不共享,从而更加安全。ContentProvider 是 Android 中一种跨程序共享数据的重要组件。
1. 使用系统的 ContentProvider
系统的 ContentProvider 有很多,如通话记录,短信,通讯录等等,都需要和第三方的 App 进行共享数据。既然是使用系统的,那么 ContentProvider 的具体实现就不需要我们担心了,使用内容提供者的步骤如下:
- 获取 ContentResolver 实例
- 确定 Uri 的内容,并解析为具体的 Uri 实例
- 通过 ContentResolver 实例来调用相应的方法,传递相应的参数,但是第一个参数总是 Uri,它制定了我们要操作的数据的具体地址
可以通过读取系统通讯录的联系人信息,显示在Listview中来实践这些知识。不要忘记在读取通讯录的时候,在清单文件中要加入相应的读取权限。
2. 自定义 ContentProvider
系统的 ContentProvider 在与我们交互的时候,只接受了一个 Uri 的参数,然后根据我们的操作返回给我们结果。系统到底是如何根据一个 Uri 就能够提供给我们准确的结果呢?只有自己亲自实现一个看看了。和之前提到的一样,想重新自定义自己程序中的四大组件,就必须重新实现一个类,重写这个类中的抽象方法,在清单文件中注册,最后才能够正常使用。
重新实现 ContentProvider 之后,发现我们重写了 6 个重要的抽象方法
onCreate()query()update()insert()delete()getType()
大部分的方法在数据库那里已经见过了,他们内部的逻辑可想而知都是对数据的增删改查操作,其中这些方法的第一个参数大多都是 Uri 实例。其中有两个方法比较特殊:
onCreate()方法应该是内容提供者创建的时候所执行的一个回调方法,负责数据库的创建和更新操作。这个方法只有我们在程序中获取 ContentResolver 实例之后准备访问共享数据的时候,才会被执行。
getType()方法是获取我们通过参数传递进去的 Uri 的 MIME 类型,这个类型是什么,后面会有实例说明。
内容提供者首先要做的一个事情就是将我们传递过来的 Uri 解析出来,确定其他程序到底想访问哪些数据。Uri 的形式一般有两种:
- 以路径名为结尾,这种 Uri 请求的是整个表的数据,如:
content://com.demo.androiddemo.provider/table1标识我们要访问 table1 表中所有的数据。 - 以 id 列值结尾,这种Uri请求的是该表中和其提供的列值相等的单条数据。
content://com.demo.androiddemo.provider/table1/1标识我们要访问 table1 表中 _id 列值为 1 的数据。
如果是内容提供器的设计者,那么我们肯定知道这个程序的数据库是什么样的,每一张表,或者每一张表中的 _id 都应该有一个唯一的内容 Uri 。我们可以将传递进来的 Uri 和我们存好的 Uri 进行匹配,匹配到了之后,就说明数据源已经找到,便可以进行相应的增删改查操作。
最后
四大组件的学习可以参考第一行代码里面的知识点,都是比较基础的东西,自己码出来练习练习即可,没必要死记,需要用到时脑袋里有这么个印象然后知道哪里可以查就行了,久而久之自然而然就记住了。