第6章实验    广播组件与通知

一、知识要点

二、项目及其源代码

创建项目Example6

(一)example6_1模块(短信来时播放音乐)

- 先准备一个名为white.mp3,存放至res/raw文件夹里。

1. 创建广播接受者组件:SmsReceiver.java

右键程序包名->New->Other->Broadcast Receiver,输入广播接收者组件名称SmsReceiver。

组件需要重写抽象基类的onReceive()方法,代码如下:

    public class SmsReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            MediaPlayer mediaPlayer = MediaPlayer.create(context,R.raw.white);
            mediaPlayer.start();
        }
    }

2. 在清单文件AndroidManifest.xml里,需要开发者手工添加注册短信接收权限、配置短信广播接收意图过滤器的代码:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.example6_1">

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

        <application
            .......
            <receiver
                android:name=".SmsReceiver"
                android:enabled="true"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
                </intent-filter>
            </receiver>

        </application>
    </manifest>

3. 主界面程序MainActivity需要写检测短信权限(属于危险权限)及请求申请本权限的代码:

/*
  以静态注册方式(在清单文件注册),使用系统短信广播示例。
  功能:接收短信广播后,自行播放音乐。
  测试方法:部署到Android 6.0模拟器,运行时授权短信权限->按Home键->运行系统的短信程序,
            向本机(号码为5554)随便发送一条短信后,广播接收者程序将被激活(播放音乐)。
 */
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,new String[]{"android.permission.RECEIVE_SMS"},1);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case 1:
                if(grantResults[0]!=PackageManager.PERMISSION_GRANTED){
                    Toast.makeText(this, "未授权,无法实现预定的功能!", Toast.LENGTH_SHORT).show();
                    finish();
                }
        }
    }

    @Override
    protected void onDestroy() {  //按手机返回键时触发
        super.onDestroy();
        //创建组件对象
        ComponentName receiver = new ComponentName(this,SmsReceiver.class);
        //获取包管理器对象
        PackageManager pm = getPackageManager();
        //禁用一个静态注册的广播接收者
        pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
    }
}

example6_1a模块(短信接收处理程序)

/*
    本短信接收者程序与手机自带的短信接收程序分别起作用
    发送方的一条短信可能被分割、分多次发送
    当短信广播到来时,在MainActivity里显示一个包含短信信息的Toast
*/
public class SmsReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //获取发送短信意图对象携带的数据
        Bundle bundle = intent.getExtras();
        if(bundle != null){
            //可使用动态调试,查看短信数据结构
            Object[] pdus = (Object[]) bundle.get("pdus");
            //发送方的一条短信可能被分割、分多次发送
            SmsMessage[] msgs = new SmsMessage[pdus.length];
            for (int i = 0; i < msgs.length; i++) {
                byte[] pdu = (byte[]) pdus[i];
                //获取分段的短信
                msgs[i]= SmsMessage.createFromPdu(pdu);
            }
            //构建短信相关信息字符串
            StringBuilder strb=new StringBuilder();
            for(SmsMessage msg:msgs){
                strb.append("\n发短信人电话:\n")
                        .append(msg.getDisplayOriginatingAddress())
                        .append("\n短信内容:\n")
                        .append(msg.getMessageBody());
                //接收时间
                Date date=new Date(msg.getTimestampMillis());
                SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                strb.append("\n短信接收时间:\n").append(sdf.format(date));
            }
            //在context指示的上下文(就是模块的MainActivity)里打Toast消息
            Toast.makeText(context, strb, Toast.LENGTH_LONG).show();
        }
    }
}

【Return Top】

(二)example6_2模块

特点:短信到来时自动产生的系统广播→激活音乐播放服务程序→活动组件程序使得停止按钮可用。

1. 布局文件:activity_main.xml

    只有一个Button:id为btnStop   text为Stop Music!

2. 音乐服务文件:MyAudioService.java

    public class MyAudioService extends Service {
        MediaPlayer mediaPlayer;

        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
        @Override
        public void onCreate() {
            mediaPlayer  = MediaPlayer.create(this,R.raw.white);
            mediaPlayer.start();
        }
        @Override
        public void onDestroy() {
            mediaPlayer.stop();
        }
    }

3. 主界面程序:MainActivity

    /*
    由于手机允许向自身发送短信,因此,使用模拟器测试时,只需向5554发送,避免开2个模拟器。
    当短信到来时,启动音乐播放服务,当前活动组件里的停止按钮可用
    测试本项目,需要卸载应用(模块)example6_1,不然有干扰(两个应用都接收到了短信并处理)
     */
    public class MainActivity extends AppCompatActivity {

        private Button btnStop;
        private boolean isCast; //是否为广播激活

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED){
                ActivityCompat.requestPermissions(this,new String[]{"android.permission.RECEIVE_SMS"},1);
            }
            btnStop=findViewById(R.id.btnStop);
            Intent intent = getIntent(); //获取广播意图对象
            isCast = intent.getBooleanExtra("iscast", false);  //默认值为false
            btnStop.setEnabled(isCast);   //设置停止按钮可用和单击监听
            if(isCast) Toast.makeText(this, "正在播放音乐...", Toast.LENGTH_SHORT).show();
            btnStop.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v){
                    //显式服务调用意图(非绑定式)
                    Intent intent=new Intent(MainActivity.this,MyAudioService.class);
                    //在Activity组件里,停止音乐播放服务
                    stopService(intent);
                    finish();  //销毁本活动
                }
            });
        }

        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            switch (requestCode){
                case 1:
                    if(grantResults[0]!=PackageManager.PERMISSION_GRANTED){
                        Toast.makeText(this, "未授权,无法实现预定的功能!", Toast.LENGTH_SHORT).show();
                        finish();
                    }else{
                        Toast.makeText(this, "请发一条短信验证...", Toast.LENGTH_SHORT).show();
                    }
            }
        }
    }

4. 广播接受者程序:SmsReceiver.java

/*
    本广播接收者程序分别调用了应用的主Activity程序和播放音乐的服务程序
*/
public class SmsReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        Intent serviceIntent = new Intent(context, MyAudioService.class);
        //在广播组件里,通过上下文对象启动音乐播放服务组件
        context.startService(serviceIntent);

        //新建调用Activity组件的意图
        Intent activityIntent = new Intent(context, MainActivity.class);
        activityIntent.putExtra("iscast", true);  //携带数据
        //新建栈用来存放被启动的Activity(当已经存在时,只做移动处理)
        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //在广播组件里,通过上下文对象启动Activity组件
        context.startActivity(activityIntent);
    }
}

注意:广播接收者组件创建后,需要在清单文件里配置短信权限和广播接收者组件的意图过滤器。 【Return Top】

(三)example6_3模块(发送自定义广播)

1. 布局文件:activity_main.xml

    1个Button+1个TextView

2. 主界面程序:MainActivity

/*
    运行时动态发送广播
    动态注册广播接收者:在程序里动态注册一个广播接收者,不同于在清单文件里静态注册广播接收者
    发送广播时,可以携带Bundle类型的数据供广播接收者程序使用
    广播发送到接收可能存在一定的延迟
    抽象类BroadcastReceiver包含抽象方法onReceive()
 */
public class MainActivity extends AppCompatActivity {
    private TextView tv;  //用于显示接收到的广播信息
    private BroadcastReceiver myReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle mybundle =  intent.getExtras();
            String str1 = mybundle.getString("data1");
            String str2 = mybundle.getString("data2");
            Toast.makeText(context, str1+" " +str2,Toast.LENGTH_LONG).show();
            tv.setText(str1+" " +str2);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //使用意图过滤器类IntentFilter动态注册广播接收者
        IntentFilter myintentfliter = new IntentFilter();
        myintentfliter.addAction("com.example.broadcast.MY_BROADCAST");
        registerReceiver(myReceiver, myintentfliter); 

        tv = findViewById(R.id.tv);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                Intent myintent = new Intent("com.example.broadcast.MY_BROADCAST");
                Bundle  bundle = new Bundle();
                bundle.putString("data1", "自定义广播与接收案例");
                bundle.putString("data2", "张粤~...0v0...");
                myintent.putExtras(bundle);  //捆绑数据
                sendBroadcast(myintent);   //发送广播
                try {
                    Thread.sleep(1000);  //休眠一下,模拟广播可能存在的延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

3.扩展练习:以静态注册方式,接收和处理自定义的广播。

【Return Top】

(四)example6_4模块(通知管理与PendingIntent的使用)

1. 布局文件:activity_main.xml

    1个TextView,其id为tv,默认不显示,在单击发通知后显示
    1个id为btn_notification的Button,负责发通知或取消通知

2. 主界面程序:MainActivity(下面的代码适用于Android 8.0,向下兼容

/*
    通过内部类Notification.Builder来构建通知(图标、标题和内容)
    (NotificationManager) getSystemService(NOTIFICATION_SERVICE)得到通知管理器对象,具有发通知和取消通知的方法
    延期意图PendingIntent使得在用户单击通知标题后查看通知内容成为可能
*/
public class MainActivity extends AppCompatActivity {
    NotificationManager notificationManager;  //通知管理器
    NotificationCompat.Builder builder;  //通知构造器(与Android版本相关)
    Button btn_notification;  //按钮
    boolean isCreate = false; //通知未创建

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

        btn_notification = findViewById(R.id.btn_notification);
        //创建设置意图对象
        Intent intent = new Intent(Settings.ACTION_SETTINGS);
        //创建延期意图对象
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
        //获取通知管理器
        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        //使用通知构造器
        builder = new NotificationCompat.Builder(this,getPackageName());
        //构建通知内容
        builder.setSmallIcon(R.drawable.notification)  //图标使用矢量图形
                .setContentTitle("设置")
                .setContentText("点击进入设置程序")
                .setWhen(System.currentTimeMillis())
                .setDefaults(Notification.DEFAULT_SOUND)
                .setContentIntent(pendingIntent);  //关键方法
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //Android 8.0
            NotificationChannel channel = new NotificationChannel( //Android 8.0需要创建通知频道
                    getPackageName(),
                    "MusicNotify",
                    NotificationManager.IMPORTANCE_DEFAULT
            );
            notificationManager.createNotificationChannel(channel);
        }

        btn_notification.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isCreate = !isCreate;  //在创建与取消之间切换
                TextView tv=findViewById(R.id.tv);
                if(isCreate) {  //如果准备创建通知
                    //发送通知至通知栏
                    notificationManager.notify(1, builder.build());  //发通知
                    tv.setVisibility(View.VISIBLE);  //设置可见
                    btn_notification.setText("取消通知");
                }else {
                    notificationManager.cancel(1);  //取消通知
                    btn_notification.setText("创建通知");
                    tv.setVisibility(View.INVISIBLE);  //设置不可见
                }
            }
        });
    }
}

【Return Top】