阅读 278

(一) Android 四大组件

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 结束前,移除广播接收器。 静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
  • 当广播为有序广播时:
  1. 优先级高的先接收
  2. 同优先级的广播接收器,动态优先于静态
  3. 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。
  • 当广播为普通广播时:
  1. 无视优先级,动态广播接收器优先于静态广播接收器
  2. 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。

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 的形式一般有两种:

  1. 以路径名为结尾,这种 Uri 请求的是整个表的数据,如: content://com.demo.androiddemo.provider/table1标识我们要访问 table1 表中所有的数据。
  2. 以 id 列值结尾,这种Uri请求的是该表中和其提供的列值相等的单条数据。content://com.demo.androiddemo.provider/table1/1 标识我们要访问 table1 表中 _id 列值为 1 的数据。

如果是内容提供器的设计者,那么我们肯定知道这个程序的数据库是什么样的,每一张表,或者每一张表中的 _id 都应该有一个唯一的内容 Uri 。我们可以将传递进来的 Uri 和我们存好的 Uri 进行匹配,匹配到了之后,就说明数据源已经找到,便可以进行相应的增删改查操作。

最后

四大组件的学习可以参考第一行代码里面的知识点,都是比较基础的东西,自己码出来练习练习即可,没必要死记,需要用到时脑袋里有这么个印象然后知道哪里可以查就行了,久而久之自然而然就记住了。

关注下面的标签,发现更多相似文章
评论