《第一行代码》读书笔记(五)----广播

简介

Android中每个应用程序都可以对自己感兴趣的广播进行注册, 这样该程序就只会接收到来自系统或者其他应用程序的自己所关心的广播内容. Android中允许应用程序自由地发送和接收广播. 发送广播借助 Intent , 接受广播需要广播接收器(Broadcast Receiver).

广播的类型

  • 标准广播(Normal broadcast)
    • 一种完全异步执行的广播, 发出后所有的广播接收器几乎都会在同一时刻接收到. 没有任何先后顺序可言. 效率比较高, 但是它无法被截断.
  • 有序广播(Ordered broadcast)
    • 一种同步执行的广播, 发出后, 同一时刻只有一个广播接收器能够收到, 在这个接收器中的逻辑执行完毕后, 广播才会继续传递. 有先后顺序, 可以截断.

接收系统广播

注册广播方式有两种, 在代码中注册(动态注册), 在 AndroidManifest.xml 中注册(静态注册).

动态注册监听网络变化

新建一个类, 继承自 BroadcastReceiver , 并重写onReceive()方法. 这样, 当广播到来时, onReceive()方法就会得到执行.

public class MainActivity extends Activity {

    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //添加响应的action来接受想要监听的系统广播
        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 connectivityManager =
                    (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

            //getActiveNetworkInfo(): 得到活动的网络信息
            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();
            }
        }
    }
}

不要忘了添加权限, Android Studio 中也是在清单文件中添加权限:

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

静态注册实现开机启动

动态注册广播可以自由控制注册与注销, 但是必须要在程序启动之后才能接收到广播. 如果想要在程序未启动的情况下接受广播, 就需要静态注册.

需求: 接受一条开机广播, 收到这条广播时就可以在onReceive()方法里执行相应逻辑, 实现开机启动功能.

新建类:

public class BootCompleteReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Boot Complete", Toast.LENGTH_SHORT).show();
    }
}

AndroidManifest.xml文件中注册广播, <application>标签内:

<receiver android:name=".BootCompleteReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

再添加权限:

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

使用广播接收器时要注意: 不要在onReceive()方法中添加过多的逻辑或者进行任何耗时操作, 因为在广播接收器中是不允许开启线程的. 当onReceive()方法运行了较长时间而没有结束时, 程序就会报错.

广播接收器更多的是扮演一种打开程序其他组件的角色, 比如创建一条状态栏通知, 或者启动一个服务等.

发送自定义广播

发送标准广播

先定义一个广播接收器:

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
    }
}

在清单文件中注册这个广播接收器, 要接受的广播的值由自己指定:

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
        <action android:name="me.zipstream.broadcast.MY_BROADCAST" />
    </intent-filter>
</receiver>

然后修改主活动的布局文件, 定义一个按钮, 用于发送广播.

最后修改MainActivity:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sendBroadcast(new Intent("me.zipstream.broadcast.MY_BROADCAST"));
            }
        });
    }
}

发送有序广播

广播是一种可以跨进程的通信方式, 因此在我们的应用程序发出的广播, 其他的应用程序也是可以收到的.发送有序广播.
修改 MainActivity 中的代码, 将sendBroadcast(new Intent("me.zipstream.broadcast.MY_BROADCAST")); 修改为:

Intent intent = new Intent("me.zipstream.broadcast.MY_BROADCAST");
sendOrderedBroadcast(intent, null);

在清单文件中通过android:priority属性给广播接收器设置优先级, 优先级比较高的广播接收器先收到广播:

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter android:priority="100">
        <action android:name="me.zipstream.broadcast.MY_BROADCAST" />
    </intent-filter>
</receiver>

于是, MyBroadcastReceiver 就可以选择是否允许广播继续传递了, 如果要将这条广播截断, 可以修改它的代码为:

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }
}

使用本地广播

以上发送和收到的广播都属于系统全局广播, 可以被其他任何程序收到, 也可以接受其他任何程序的广播. 这样很容易引发安全性的问题. Android中引入了一套本地广播机制, 使发出的广播只能够在应用程序内部进行传递, 并且广播接收器也只能接受来自本应用程序发出的广播.
修改MainActivity 的代码:

public class MainActivity extends Activity {

    private IntentFilter intentFilter;
    private LocalBroadcastManager localBroadcastManager;
    private LocalReceiver localReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        localBroadcastManager = LocalBroadcastManager.getInstance(this);//获取实例

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("me.zipstream.broadcast.LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);//发送本地广播
            }
        });

        intentFilter = new IntentFilter();
        intentFilter.addAction("me.zipstream.broadcast.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();
        }
    }
}

本地广播的使用, 主要就是用 LocalBroadcastManager 来对广播进行管理, 并提供了发送广播和注册广播接收器的方法.

另外, 本地广播无法通过静态注册的方式来接受.

本地广播的优势:
* 不需要担心机密数据泄露.
* 不需要安全漏洞隐患.
* 比系统全局广播更高效.

广播的最佳实践—-实现强制下线功能

思路: 在界面上弹出一个对话框, 让用户无法进行其他任何操作, 点击对话框的确定按钮, 回到登录界面. 不需要在每个界面都编写一个弹出对话框的逻辑, 只需要使用广播就可以做到了.新建一个项目.

新建 ActicityCollector , 管理所有活动:

public class ActivityCollector {

    public static List<Activity> activities = new ArrayList<Activity>();

    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 Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

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

创建登录界面 login.xml :

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:stretchColumns="1">

    <TableRow>

        <TextView
            android:layout_height="wrap_content"
            android:text="Account:" />

        <EditText
            android:id="@+id/account"
            android:layout_height="wrap_content"
            android:hint="Input your account" />
    </TableRow>

    <TableRow>

        <TextView
            android:layout_height="wrap_content"
            android:text="Password:" />

        <EditText
            android:id="@+id/password"
            android:layout_height="wrap_content"
            android:inputType="textPassword" />
    </TableRow>

    <TableRow>

        <Button
            android:id="@+id/login"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:text="Login" />
    </TableRow>

</TableLayout>

新建 LoginActivity 继承自 BaseActivity :

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.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();

                if (account.equals("admin") && password.equals("123456")) {
                    startActivity(new Intent(LoginActivity.this, MainActivity.class));
                    finish();
                } else {
                    Toast.makeText(LoginActivity.this,
                            "account or password is invalid", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}

修改清单文件, 将LoginActicity 作为启动活动:

<activity
    android:name=".LoginActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

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

修改 activity_main.xml 代码:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

</LinearLayout>

修改 MainActivity 的代码:

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button forceOffline = (Button) findViewById(R.id.force_offline);
        forceOffline.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("me.zipstream.broadcastbp.FORCE_OFFLINE");
                sendBroadcast(intent);
            }
        });
    }
}

注意, 强制下线的逻辑并不是写在 MainActivity 中, 而是写在 广播接收器 中. 新建 ForceOfflineReceiver , 继承自 BroadcastReceiver :

public class ForceOfflineReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, Intent intent) {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
        dialogBuilder.setTitle("Warning");
        dialogBuilder.setMessage("You are forced to be offline. Please try to login again");
        dialogBuilder.setCancelable(false);//一定不能取消
        dialogBuilder.setPositiveButton("OK",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ActivityCollector.finishAll();//销毁所有活动
                        Intent intent1 = new Intent(context, LoginActivity.class);
                        //在广播接收器中启动活动, 要加入这个标志
                        intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        context.startActivity(intent1);//重新启动LoginActivity
                    }
                });

        AlertDialog alertDialog = dialogBuilder.create();

        //需要设置AlertDialog的类型, 保证在广播接收器中可以正常弹出
        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);

        alertDialog.show();
    }
}

修改清单文件, 添加权限, 并注册广播接收器:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
...
<receiver android:name=".ForceOfflineReceiver">
    <intent-filter>
        <action android:name="me.zipstream.broadcastbp.FORCE_OFFLINE" />
    </intent-filter>
</receiver>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值