淘先锋技术网

首页 1 2 3 4 5 6 7

目录

1. 广播机制简介

2. 接收系统广播

2.1 动态注册监听网络变化(代码中注册)

2.2 静态注册实现开机启动

# 问题:没有收到开机广播(已解决)

3. 发送自定义广播

3.1 发送标准广播

# 问题:接收不到标准广播(已解决)

# 问题:程序A发广播,程序B收不到(已解决)

3.2 发送有序广播

4. 使用本地广播:安全性考虑

5. 实践——强制下线功能


1. 广播机制简介

    Broadcast:应用质检传输信息的机制

    BroadcastReceiver:对发送出来的广播过滤并响应的组件,用来接收来自系统和应用中的广播。

    用途:开机完成后、网络状态改变时、电池电量改变时,系统产生一条广播

 

why

        广播的发送者和接收者事先是不需要知道对方的存在的,这样带来的好处便是,系统的各个组件可以松耦合地组织在一起,这样系统就具有高度的可扩展性,容易与其它系统进行集成。

    减少工作量,作为应用开发者,只需掌握BroadcastReceiver

 

2. 接收系统广播

开机完成后、网络状态改变时、电池电量改变时,系统产生一条广播

 

2.1 动态注册监听网络变化(代码中注册)

 

 

 

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) {
            Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
        }
    }
}

Notes:

① 内部类 NetworkChangeReceiver 继承自BroadcastReceiver,重写 onReceive。每当网络发生变化,onReceive 就会执行。

② 创建 IntentFilter ,添加 action,当网络状态变化时,系统会发出一条值为 ...CONNECTIVITY_CHANGE 的广播。也就是说想监听什么广播,这里就添加相应的 action

③ registerReceiver 注册广播接收器,传入广播接收器(networkChangeReceiver)实例和意图过滤器(intentFilter)实例。

④ 动态注册一定要注销才行,onDestroy 方法中

 

准确告诉用户有网还是没网,修改广播接收器

class NetworkChangeReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        ConnectivityManager connectivityManager = (ConnectivityManager)
                getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        if (networkInfo != null && networkInfo.isAvailable()){
            Toast.makeText(context,"network is available",
                    Toast.LENGTH_SHORT).show();
        }else {
            Toast.makeText(context,"network is unavailable",
                    Toast.LENGTH_SHORT).show();
        }
    }
}

Notes:

① ConnectivityManager 是系统服务类,专门管理网络连接。拿到 NetworkInfo 实例判断是否有网

② 访问系统网络状态,AndroidManifest.xml 声明权限

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

 

 

2.2 静态注册实现开机启动

动态注册能自由注册注销,灵活性有优势,但是有一个缺点,必须在程序启动之后才能接收到广播。因为注册在 onCreate

1. 新建广播接收器

 

Exported:是否允许接收本程序以外的广播

Enabled:是否启用这个广播接收器

 

public class BootCompleteReceiver extends BroadcastReceiver {
    private static final String TAG = "BootCompleteReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e(TAG, "onReceive: Boot Complete!");
        Toast.makeText(context, "Boot Complete!", Toast.LENGTH_SHORT).show();
    }
}

注册这一步已经自动完成了,还需要添加接收开机权限,和广播接收器接收对应的 action

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<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>

长按电源键会出现关机跟重启的选项。

 

# 问题:没有收到开机广播(已解决)

 

原因:接收到了,但是机器需要时间反应,得等半分钟左右。这里不适合用 Toast,写条log 好点,不用盯着等

 

Notes:

① BroadcastReceiver需要注册(静态注册、代码注册)

② BroadcastReceiver生命周期只有十秒左右,所以在BR里不能做些耗时操作,应通过发送Intent给Service,由Service来完成

③ BR中不允许开启线程

 

3. 发送自定义广播

 

3.1 发送标准广播

1.新建 MyNormalReceiver

public class MyNormalReceiver extends BroadcastReceiver {
    private static final String TAG = "MyNormalReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive: ");
        Toast.makeText(context, "received in MyNormalReceiver!", Toast.LENGTH_LONG).show();
    }
}

2.注册广播

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

3.发送广播

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
        sendBroadcast(intent);
        Log.d(TAG, "onClick: ");
    }
});

 

# 问题:接收不到标准广播(已解决)

 

原因:Android8.0 为了提高电池续航时间、提高设备性能,系统限制未在前台运行的应用某些行为,如:

后台运行应用对后台服务的访问受到限制

应用无法使用清单注册的大部分隐式广播。

 

解决:设置参数 Component,精准定位要接收广播的接收器

参数1:应用包名

参数2:Receiver 所在的完整路径

Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
intent.setComponent(new ComponentName("com.example.broadcasttest",
        "com.example.broadcasttest.MyNormalReceiver"));
sendBroadcast(intent);

 

 

# 问题:程序A发广播,程序B收不到(已解决)

问题:新建应用Broadcast2,使用同样的 action 静态注册广播接收器。A发广播,B收不到

 

 

解决:这里需要 addFlag,而且不需要 setComponent

Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
if(Build.VERSION.SDK_INT >= 26){
    intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
}
sendBroadcast(intent);

这个 intent 可以从以存在的活动启动一个新的活动

/**
 * If set and this intent is being used to launch a new activity from an
 * existing one, the current activity will not be counted as the top
 * activity for deciding whether the new intent should be delivered to
 * the top instead of starting a new one.  The previous activity will
 * be used as the top, with the assumption being that the current activity
 * will finish itself immediately.
 */
public static final int FLAG_ACTIVITY_PREVIOUS_IS_TOP = 0x01000000;

 

3.2 发送有序广播

sendOrderedBroadcast(intent, null);

参数1不变

参数2:与权限有关的字符串,这里传 null

重新运行后,俩应用正常收到广播

 

在A收到广播后,截断广播:

1. 给广播接收器设置优先级

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

2. 设置截断广播

@Override
public void onReceive(Context context, Intent intent) {
    Log.d(TAG, "onReceive: ");
    Toast.makeText(context, "received in MyNormalReceiver!", 
            Toast.LENGTH_SHORT).show();
    abortBroadcast();
}

此时后面的广播接收器再无法接收到这条广播

 

 

4. 使用本地广播:安全性考虑

发出的广播只能在应用内部传递,并且广播接收器只能接收来自本应用发出的广播。

 

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "luo-MainActivity";
    public static final String LOCAL_BROADCAST = "com.example.broadcasttest2.LOCAL_BROADCAST";
    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(LOCAL_BROADCAST);
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction(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) {
            Log.e(TAG, "onReceive: ------------------");
            Toast.makeText(context, "received local broadcast",
                    Toast.LENGTH_LONG).show();
        }
    }
}

Note:使用 LocalBroadcastManager 来实现发送广播、注册和注销广播接收器

 

5. 实践——强制下线功能

效果:

1. 输入 admin,密码 123456,点击登录到主界面

 

2. 点击发送广播按钮,发出强制下线的广播,弹出提示框

 

3. 点 OK,退回到登录界面

 

重要代码:BaseActivity

public class BaseActivity extends AppCompatActivity {
    private ForceOffLineReceiver receiver;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivtiy(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(MainActivity.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 be 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();
        }
    }
}

onCreate, onDestroy 管理活动

onResume, onPause 注册注销广播

理由:需要保证只有处于栈顶的活动,才能接收到强制下线广播,没栈顶活动没必要管这个。所以,当一个活动失去栈顶位置时就会自动注销广播接收器。