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 中注册
复制代码
- 在 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 值。
复制代码
静态注册与动态注册的区别
- 动态注册广播不是常驻型广播,也就是说广播跟随 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 进行匹配,匹配到了之后,就说明数据源已经找到,便可以进行相应的增删改查操作。
最后
四大组件的学习可以参考第一行代码里面的知识点,都是比较基础的东西,自己码出来练习练习即可,没必要死记,需要用到时脑袋里有这么个印象然后知道哪里可以查就行了,久而久之自然而然就记住了。