8002117067 徐梓涵 软件工程1702


什么是广播?

广播分为两大类:标准广播 和 有序广播

  • 标准广播

是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接受到这条广播消息,因此它们之间没有任何先后顺序可言,这种广播的效率会比较高,但同时也意味着它是无法被拦截的。 image.png

  • 有序广播

是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接受器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。 image.png

1.广播与后台服务如何配合使用

这里做一个简单的案例来演示一下广播和后台服务是如何配合使用的:
首先我们使用上次我做的一个简单的服务案例:
就是点击按钮服务启动,再次点击按钮服务关闭:

  • MainActivity.java中的按钮启动服务操作:

image.png

  • MyService.java中的创建服务时候启动我们的通知,这样我们可以清除的知道服务是否启动了,服务是否关闭了:

MyService.java:

  1. @Override
  2. public void onCreate() {
  3. super.onCreate();
  4. //发送广播
  5. Intent broadintent = new Intent("com.ncu.servicetest.Broadcast");
  6. sendBroadcast(broadintent);
  7. }
  8. @Override
  9. public int onStartCommand(Intent intent, int flags, int startId) {
  10. return super.onStartCommand(intent, flags, startId);
  11. }
  12. @Override
  13. public void onDestroy() {
  14. super.onDestroy();
  15. Intent broadintent = new Intent("com.ncu.servicetest.Broadcast2");
  16. sendBroadcast(broadintent);
  17. }
  • 然后我们需要写通知接收器,来接受我们对于的通知,并且进行相对应的操作:

Broadcast.java:

  1. package com.ncu.servicetest;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.widget.Toast;
  6. //写一个接收器广播接收服务启动时候的广播
  7. public class Broadcast extends BroadcastReceiver {
  8. @Override
  9. public void onReceive(Context context, Intent intent) {
  10. //发出短消息
  11. Toast.makeText(context, "服务启动", Toast.LENGTH_LONG).show();
  12. }
  13. }

Broadcast2.java:

  1. package com.ncu.servicetest;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.widget.Toast;
  6. public class Broadcast2 extends BroadcastReceiver {
  7. @Override
  8. public void onReceive(Context context, Intent intent) {
  9. //发出短消息
  10. Toast.makeText(context, "服务关闭", Toast.LENGTH_LONG).show();
  11. }
  12. }
  • 最后不要忘记去AndroidMainfest.java中注册一下,不然是不会被接收通知的:
    1. <receiver
    2. android:name=".Broadcast"
    3. android:enabled="true"
    4. android:exported="true">
    5. <intent-filter>
    6. <action android:name="com.ncu.servicetest.Broadcast"/>
    7. </intent-filter>
    8. </receiver>
    9. <receiver
    10. android:name=".Broadcast2"
    11. android:enabled="true"
    12. android:exported="true">
    13. <intent-filter>
    14. <action android:name="com.ncu.servicetest.Broadcast2"/>
    15. </intent-filter>
    16. </receiver>
    我觉得安卓中对广播和服务的配合可以用在很多地方,比如服务进行到什么步骤了,可以通过通知机制来进行告知用户,或者比如用户有一个还没有完成的操作需要提醒也可以使用这样的机制。
    最后看看项目的演示效果:
    当我们点击START SERVICE时候就会发出一个通知来告知用户服务启动了。
    image.png
    当我们点击STOP SERVICE的时候:
    image.png
    当然上面只是简单的案例演示。

    2.当一个耗时的操作完成操作后,如何利用广播及时通知Activity更新UI:

    当我们完成了一个比较耗时的操作之后,我们可以使用广播来对Activiity中的UI进行更新的操作:
    首先我们写一个简单的服务来进行不断的发出服务:
    MySerView.java: ```xml package com.ncu.uibroadcast;

import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log;

public class MyService extends Service { public MyService() { } //判断停止的标志 boolean isStop=false;

  1. @Override
  2. public IBinder onBind(Intent intent) {
  3. // TODO: Return the communication channel to the service.
  4. throw new UnsupportedOperationException("Not yet implemented");
  5. }
  6. public void onCreate(){
  7. Log.i("TAG","Services onCreate");
  8. super.onCreate();
  9. }
  10. public void onStart(Intent intent,int startId){
  11. Log.i("TAG","Services onStart");
  12. super.onStart(intent, startId);
  13. new Thread(){//新建线程,每隔1秒发送一次广播,同时把i放进intent传出
  14. public void run(){
  15. int i=0;
  16. while(!isStop){
  17. Intent intent=new Intent();
  18. intent.putExtra("i", i);
  19. i++;
  20. intent.setAction("com.ncu.uibroadcast.MyReceiver");//action与接收器相同
  21. sendBroadcast(intent);
  22. Log.i("TAG",String.valueOf(i));
  23. try {
  24. sleep(1000);
  25. } catch (InterruptedException e) {
  26. // TODO Auto-generated catch block
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. }.start();
  32. }
  33. @Override
  34. public void onDestroy() {
  35. Log.i("TAG","Services onDestory");
  36. isStop=true;//即使service销毁线程也不会停止,所以这里通过设置isStop来停止线程
  37. super.onDestroy();
  38. }

}

  1. UI界面进行按钮以及滚动条的设置:<br />activity_main.java:<br />
  2. ```xml
  3. <?xml version="1.0" encoding="utf-8"?>
  4. <androidx.constraintlayout.widget.ConstraintLayout
  5. xmlns:android="http://schemas.android.com/apk/res/android"
  6. xmlns:tools="http://schemas.android.com/tools"
  7. xmlns:app="http://schemas.android.com/apk/res-auto"
  8. android:layout_width="match_parent"
  9. android:layout_height="match_parent"
  10. tools:context=".MainActivity">
  11. <Button
  12. android:id="@+id/button2"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:text="关闭服务"
  16. app:layout_constraintBottom_toBottomOf="parent"
  17. app:layout_constraintEnd_toEndOf="parent"
  18. app:layout_constraintHorizontal_bias="0.755"
  19. app:layout_constraintStart_toStartOf="parent"
  20. app:layout_constraintTop_toTopOf="parent"
  21. app:layout_constraintVertical_bias="0.152" />
  22. <TextView
  23. android:id="@+id/textView"
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:text="0"
  27. app:layout_constraintBottom_toBottomOf="parent"
  28. app:layout_constraintLeft_toLeftOf="parent"
  29. app:layout_constraintRight_toRightOf="parent"
  30. app:layout_constraintTop_toTopOf="parent" />
  31. <ProgressBar
  32. android:id="@+id/progressBar"
  33. style="?android:attr/progressBarStyleHorizontal"
  34. android:layout_width="300dp"
  35. android:layout_height="11dp"
  36. app:layout_constraintBottom_toBottomOf="parent"
  37. app:layout_constraintEnd_toEndOf="parent"
  38. app:layout_constraintStart_toStartOf="parent"
  39. app:layout_constraintTop_toTopOf="parent"
  40. app:layout_constraintVertical_bias="0.406" />
  41. <Button
  42. android:id="@+id/button"
  43. android:layout_width="wrap_content"
  44. android:layout_height="wrap_content"
  45. android:text="开启服务"
  46. app:layout_constraintBottom_toBottomOf="parent"
  47. app:layout_constraintEnd_toEndOf="parent"
  48. app:layout_constraintHorizontal_bias="0.291"
  49. app:layout_constraintStart_toStartOf="parent"
  50. app:layout_constraintTop_toTopOf="parent"
  51. app:layout_constraintVertical_bias="0.153" />
  52. </androidx.constraintlayout.widget.ConstraintLayout>

最后在Activity中写一个广播接收器,并且绑定两个按钮设置监听启动服务以及监听器还有关闭服务以及关闭监听器:
MainActivity.java:

  1. package com.ncu.uibroadcast;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import android.content.BroadcastReceiver;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.IntentFilter;
  7. import android.os.Bundle;
  8. import android.view.View;
  9. import android.widget.Button;
  10. import android.widget.ProgressBar;
  11. import android.widget.TextView;
  12. public class MainActivity extends AppCompatActivity {
  13. Button b1,b2;
  14. ProgressBar pb;
  15. TextView tv;
  16. MyReceiver receiver;
  17. @Override
  18. protected void onCreate(Bundle savedInstanceState) {
  19. super.onCreate(savedInstanceState);
  20. setContentView(R.layout.activity_main);
  21. b1=(Button)findViewById(R.id.button);
  22. b2=(Button)findViewById(R.id.button2);
  23. b1.setOnClickListener(new View.OnClickListener() {
  24. @Override
  25. public void onClick(View v) {
  26. startService(new Intent(MainActivity.this, MyService.class));
  27. //开始服务
  28. receiver=new MyReceiver();
  29. //这么写就可以不用再AndroidManifest中注册了,直接可以
  30. IntentFilter filter=new IntentFilter();
  31. filter.addAction("com.ncu.uibroadcast.MyReceiver");
  32. MainActivity.this.registerReceiver(receiver,filter);
  33. }
  34. });
  35. b2.setOnClickListener(new View.OnClickListener() {
  36. @Override
  37. public void onClick(View v) {
  38. stopService(new Intent(MainActivity.this, MyService.class));
  39. MainActivity.this.unregisterReceiver(receiver);
  40. }//关闭服务
  41. });
  42. pb=(ProgressBar)findViewById(R.id.progressBar);
  43. tv=(TextView)findViewById(R.id.textView);
  44. }
  45. public class MyReceiver extends BroadcastReceiver {
  46. @Override
  47. public void onReceive(Context context, Intent intent) {
  48. System.out.println("OnReceiver");
  49. //广播进行发送到UI
  50. Bundle bundle=intent.getExtras();
  51. int a=bundle.getInt("i");
  52. pb.setProgress(a);
  53. tv.setText(String.valueOf(a));
  54. //处理接收到的内容
  55. }
  56. }
  57. }

最后我们来看一下效果:
启动服务前
image.png
启动服务后:
进度条不断加一:
image.png

3.在Android老版本中,利用系统广播很容易实现的功能,如App的开机自动启动功能,Android O以后实现起来就没有那么容易,请探究一下Android O以后的变化:

使用静态的系统广播接收的方式:

接收广播处理信息广播接收器
BootCompleteReceiver.java:

  1. package com.ncu.openbroadcast;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.widget.Toast;
  6. public class BootCompleteReceiver extends BroadcastReceiver {
  7. @Override
  8. public void onReceive(Context context, Intent intent) {
  9. // TODO: This method is called when the BroadcastReceiver is receiving
  10. // an Intent broadcast.
  11. //throw new UnsupportedOperationException("Not yet implemented");
  12. Toast.makeText(context,"Boot Complete",Toast.LENGTH_LONG).show();
  13. }
  14. }

AndroidMainifest.xml:权限声明以及接收系统广播:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.ncu.openbroadcast">
  4. <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
  5. <application
  6. android:allowBackup="true"
  7. android:icon="@mipmap/ic_launcher"
  8. android:label="@string/app_name"
  9. android:roundIcon="@mipmap/ic_launcher_round"
  10. android:supportsRtl="true"
  11. android:theme="@style/AppTheme">
  12. <receiver
  13. android:name=".BootCompleteReceiver"
  14. android:enabled="true"
  15. android:exported="true">
  16. <intent-filter>
  17. <action android:name="android.intent.action.BOOT_COMPLETED" />
  18. <category android:name="android.intent.category.LAUNCHER" />
  19. <category android:name="android.intent.category.HOME" />
  20. </intent-filter>
  21. </receiver>
  22. <activity android:name=".MainActivity">
  23. <intent-filter>
  24. <action android:name="android.intent.action.MAIN" />
  25. <category android:name="android.intent.category.LAUNCHER" />
  26. </intent-filter>
  27. </activity>
  28. </application>
  29. </manifest>

老版本中测试成功:
image.png
网上说新的版本中要将自己程序变成系统程序才可以开机启动:
读书笔记-Android广播 - 图9
使用系统签名或者使用变成系统程序:
一:
仅仅修改AndroidManifest.xml,接着用系统签名。
1.下载工具到电脑本地:https://github.com/YuqingCN/keytool-importkeypair
2.从源码/build/target/product/security中找到图片中的文件,把一下内容放到keytool-importkeypair目录下。
3.打开终端,cd到上面的目录,执行:keytool-importkeypair -k 签名文件 -p 123456 -pk8 platform.pk8 -cert platform.x509.pem -alias 别名名称
提示:签名文件。可以用AS生成一个自己的签名文件,然后把这个签名文件放到keytool-importkeypair目录下
123456 是密码
4.终端命令执行后,签名文件就带有系统签名了。
5.Android studio 编译app时候使用带有系统签名的文件来签名。
二:
xml中写发如下:主要是红色圈圈部分内容
读书笔记-Android广播 - 图10
最后:
把编译出来的app push到系统 system/app 目录下 (复位,即可看到该程序自动运行)。