第一行代码 Android读书笔记(三)

第五章 全局大喇叭-详解广播机制

广播机制简介

Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自其他应用程序的。Android提供了一套完整的API,允许应用程序自由地发送和接收广播。接收广播的方法需要引入一个新的概念-广播接收器。
Android中的广播主要分为两种类型:标准广播和有序广播。
标准广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播信息,因此它们之间没有任何先后顺序可言。这种广播的效率比较高,但同时也意味着它是无法被截断的。
有序广播是一种同步执行的广播,在广播发出之后,在同一时刻只会有一个广播接收器能够接收到这条广播消息,当这个广播接收器的逻辑执行完毕后,广播才会继续传递。此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。

接收系统广播

动态注册监听网络变化
注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml注册,其中前者被称为动态注册,后者被称为静态注册。
如何创建一个广播接收器
新建一个类,让它继承自Broadcast-Receiver,并重写父类的onReceive()方法就行了。这样当有广播到来时,onReceive()方法就会得到执行,具体的逻辑可以在该方法中处理。

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);
    }
    class NetworkChangeReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context,"network changes",Toast.LENGTH_SHORT).show();
        }
    }
}

在这里创建了一个IntentFilter的实例,并给它添加了一个值为android.net.conn.CONNECTIVITY_CHANGE的action,因为当网络状态发生变化时,系统发出的正是一条值为android.net.conn.CONNECTIVITY_CHANGE的广播,广播接收器想要监听什么广播,就在这里添加相应的action。调用registerReceiver()方法进行注册,将NetworkChangeReceiver的实例和IntentFilter的实例都传了进去
动态注册的广播一定要取消注册,在这里是在onDestory()方法中通过调用unregisterReceiver()方法来实现的。
Android系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中声明权限才可以,否则程序将会直接崩溃。打开AndroidManifest.xml文件,加入权限就可以访问<uses-permission android:name="Manifest.permission.MODIFY_AUDIO_SETTINGS" />
静态注册实现开机启动
动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但是也存在着一个缺点,即必须要在程序启动之后才能接收到广播。静态注册的方式可以让程序未启动的情况下接收到广播。
新建广播接收器:new->other->Broadcast Receiver。Exported属性表示是否允许这个广播接收器接收本程序以外的广播。Enabled属性表示是否启用这个广播接收器。
静态的广播接收器一定要在AndroidManifest.xml文件中注册才可以使用。不过由于使用软件的快捷方式创建的广播接收器,因此注册这一步已经自动完成了。

 <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true"></receiver>

此时仍然不能接收到开机广播,需要对AndroidManifest.xml文件进行修改。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>

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

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

</manifest>

发送自定义广播

发送标准广播
需要先定义一个广播接收器来准备接收此广播才行。
像上面一样新建一个广播接收器。

<receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

让MyBroadcastReceiver接收一条值为com.example.broadcasttest.MY_BROADCAST的广播。

Intent intent=new Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);

首先构建出了一个Intent对象,把要发送的广播的值传入,然后调用Context的sendBroadcast()方法将广播发送出去,这样所有监听com.example.broadcasttest.MY_BROADCAST这条广播的广播接收器就会收到消息。此时发出去的广播就是一条标准广播。
发送有序广播
sendBroadcast()方法改为sendOrderBroadcast(),第一个参数是Intent,第二个参数是与权限相关的字符串。
设定广播接收器的先后顺序,是在注册的时和设定的

<receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="100">
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

通过android:priority属性给广播接收器设置了优先级,优先级比较高的广播接收器就可以先收到广播。
广播接收器可以决定是否允许广播继续传递,在广播接收器的onReceiver()方法中abortBroadcast()表示将这条广播截断,后面的广播接收器将无法再接收到这条广播。

使用本地广播

系统全局广播即发出的广播可以被其他任何应用程序接收到,并且我们也可以接收来自其他任何应用程序的广播。这样很容易引起安全性的问题。Android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,而且广播接收器也只能接收来自本应用程序发出的广播,这样所有的安全性问题就都不存在了。
本地广播使用了一个LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播接收器的方法。

public class BroadCastActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_broad_cast);
        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) {
            Toast.makeText(context,"received local broadcast",Toast.LENGTH_SHORT).show();
        }
    }
}

本地广播是无法通过静态注册的方式来接收的,静态注册主要就是为了让程序在未启动的情况下也能收到广播。
本地广播的几点优势

  1. 可以明确地知道正在发送的广播不会离开我们的程序,因此不必担心机密数据泄漏。
  2. 其他的程序无法将广播发送到我们程序的内部,因此不需要担心会有安全漏洞的隐患
  3. 发送本地广播比发送系统全局广播将会更加高效。

使用广播实现强制下线功能

首先实现关闭所有活动的功能
先创建ActivityCollector类管理所有的活动

public class ActivityCollector {
    public static List<Activity> activities=new ArrayList<>();
    public static void addActivity(Activity activity){
        activities.add(activity);
    }
    public static  void removeActivity(Activity activity){
        activities.remove(activity);
    }
    public static void finishAll(){
        for(Activity activity:activities){
            if(!activity.isFinishing()){
                activity.finish();
            }
        }
    }
}

创建BaseActivity类作为所有活动的父类

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }
}

搭建登录
登录页面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
<LinearLayout
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="60dp"
   >
    <TextView
        android:layout_width="90dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:textSize="18sp"
        android:text="Account:"/>
    <EditText
        android:id="@+id/account"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_vertical"/>
</LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp"
     >
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Password:"/>
        <EditText
            android:id="@+id/password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword"/>
    </LinearLayout>
    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="Login"
      />
</LinearLayout>

登录活动

public class LoginActivity extends BaseActivity {

    private EditText accountEdit;

    private EditText passwordEdit;

    private Button login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        accountEdit = (EditText) findViewById(R.id.account);
        passwordEdit = (EditText) findViewById(R.id.password);
        login = (Button) findViewById(R.id.login);
        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String account = accountEdit.getText().toString();
                String password = passwordEdit.getText().toString();
                // 如果账号是admin且密码是123456,就认为登录成功
                if (account.equals("admin") && password.equals("123456")) {
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    startActivity(intent);
                    finish();
                } else {
                    Toast.makeText(LoginActivity.this, "account or password is invalid",
                            Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

}

实现强制下线,登录成功后点击主界面的按钮触发强制下线。
主界面

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

<Button
    android:id="@+id/force_offline"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="send force offline broadcast"
    tools:ignore="MissingConstraints" />
    

</androidx.constraintlayout.widget.ConstraintLayout>

修改MainActivity中的代码

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button forceOffline=findViewById(R.id.force_offline);
        forceOffline.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent("com.example.bestbroadpractice.force_offline");
                sendBroadcast(intent);

            }
        });
    }
}

在按钮的点击事件里发送了一条广播,广播的值为com.example.bestbroadpractice.force_offline,这条广播就是用于通知程序强制用户下线的,强制用户下线的逻辑并不是写于MainActivity里的,而是应该写在接收这条广播的广播接收器里面,这样强制下线的功能就不会依附于任何的界面,不管是在程序的任何地方,只需要发出这样一条广播,就可以完成强制下线的操作了。
不可能在每个活动中注册一个动态的广播接收器,可以在BaseActivity中动态注册一个广播接收器,所有的活动都是继承自BaseActivity的

package com.example.bestbroadpractice;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    private ForceOffLineReceiver receiver;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter=new IntentFilter();
        intentFilter.addAction("com.example.bestbroadpractice.force_offline");
        receiver=new ForceOffLineReceiver();
        registerReceiver(receiver,intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(receiver!=null){
            unregisterReceiver(receiver);
            receiver=null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }
    class ForceOffLineReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(final Context context, Intent intent) {
            AlertDialog.Builder builder=new AlertDialog.Builder(context);
            builder.setTitle("Warning");
            builder.setMessage("you are forced to bo offline.please try to login again.");
            builder.setCancelable(false);
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCollector.finishAll();
                    Intent intent=new Intent(context,LoginActivity.class);
                    context.startActivity(intent);
                }
            });
            builder.show();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值