📱 Android | 广播和通知使用

掌握了一些安卓的基本用法,这次来看看四大部件中的广播以及通知还有桌面小部件的使用

广播

参考资料: 官方文档

考虑到现在 Android 的版本都发布到9.0了,并且以后9.0所占的比重也会越来越多,因此基于最新的版本的 SDK 开发是最好的。

7.0开始,Android 对于广播的权限就开始收缩了,为了更好地管理后台软件以及各种唤醒,Android 在广播方面加入了许多的限制。

首先是7.0, 从7.0开始,ACTION_NEW_PICTUREACTION_NEW_VIDEO这两个系统广播不再发送,并且对于CONNECTIVITY_ACTIONAndroidManifest.xml声明的接收器不再起作用,必须使用 registerReceiver进行动态注册。

8.0开始,大多数隐式广播的接收器将不在起作用,只有当用户主动使用应用程序是,才可以使用一个 context-registered receiver

到了9.0NETWORK_STATE_CHANGED_ACTION不会接收用户位置信息或个人可识别数据,Wi-Fi相关的广播也不会发送具体的信息。

其中对我们这次实验最主要的影响就是8.0的改动,我们需要使用特殊的方法来实现我们的静态广播。

接受广播

应用程序可以通过两种方式接收广播:通过清单声明接收器和上下文注册接收器。

我们在这次实验中称之为静态广播和动态广播。

静态广播,就是在AndroidManifest.xml中声明的广播,在8.0中,我们不能通过这个方法来注册隐式广播(不是专门针对你的应用程序的广播)。因此,我们需要使用显式广播来实现。

  • 首先,在AndroidManifest.xml中指定广播接收者,并且指定接收者订阅的广播操作

    1
    2
    3
    4
    5
    <receiver android:name=".util.StaticReceiver">
    <intent-filter>
    <action android:name="cn.zhenly.experimentone.action.StaticReceiver" />
    </intent-filter>
    </receiver>
  • 然后实现广播接收者的类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class StaticReceiver extends BroadcastReceiver {
    private static final String TAG = "cn.zhenly.experimentone.ACTION";
    @Override
    public void onReceive(Context context, Intent intent) {
    if (Objects.equals(intent.getAction(), TAG)) {
    // 接收到广播之后的行为
    }
    }
    ...
    }

对于静态广播,在安装应用程序时,系统包管理器会注册接收器。如果应用程序目前没有运行,系统可以启动应用程序并发送广播。

动态广播, 在应用运行时候注册的广播接收器。

创建过程也比较简单

1
2
3
4
DynamicReceiver dynamicReceiver = new DynamicReceiver(); 			// 创建一个广播接收器的实例
IntentFilter dynamic_filter = new IntentFilter(); // 创建一个 IntentFilter
dynamic_filter.addAction(DYNAMICATION); // 添加动态广播的 Action
mContext.registerReceiver(dynamicReceiver, dynamic_filter); // 注册自定义动态广播消息

对于动态广播,如果当前的Context是有效的,那么接收器就会接收到广播。如果在Activity中注册的广播,只要该Activity没有destroy,那么就会有效。如果在Application注册的广播,那么就会在应用运行的全过程有效。

除了注册还需要反注册一波

1
mContext.unregisterReceiver(dynamicReceiver);

为了与注册对应起来,最好就是放在同一个Activity两个相对应的生命周期里面,以防止接收器从活动的上下文中泄露和多次注册。

其接收器和静态的是一样的,这里就不详细说明了。

最后,如果需要在Reciver中执行比较耗时的操作的话,需要使用goAsync()来延长时间。

发送广播

应用程序发送广播又有三种方法

  • sendOrderedBroadcast :发送有序广播
  • sendBroadcast:发送正常广播
  • LocalBroadcastManager.sendBroadcast:发送本地广播(适合于同一个应用程序间的广播,效率比较高,安全性高)

发送显式广播:

1
2
3
4
Intent intentBroadcast = new Intent();
intentBroadcast.setAction(STATICATION);
intentBroadcast.setComponent(new ComponentName(mContext.getPackageName(), StaticReceiver.class.getName())); // 显式设置广播接受器
mContext.sendBroadcast(intentBroadcast);

显式广播使用需要指定接受广播的应用的包名以及广播接收器的类名,否则对于8.0或以上的静态广播接收器是无法接受到广播的。

Note:广播的意图是无法用于启动一个activity的,广播接收器不能看到或者捕获到一个用于启动活动的意图。

通知

参考资料: 官方文档

从 Android 8.0 开始(API 级别 26) ,所有的通知都必须被分配到一个通道,否则它将不会出现。这样一来,我们就可以统一在系统设置里面控制通知的发送,妈妈再也不用担心应用随便乱发通知了。

现在,我们在系统设置中的通知随便打开一个应用,可以看到,在这里可以统一管理所有类型的通知,这些都是不同的通知通道。我们可以在这里屏蔽掉我们不需要的通知而不需要进入应用里面设置。

1540110944599

那么,如何创建一个通道呢?

参考资料:官方文档

  • 根据name,descriptionimportance构建一个 NotificationChannel
1
2
3
4
5
6
7
8
9
10
11
public static void createNotificationChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "ExperimentTwoNotify";
String description = "Show some notify";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}

根据官方文档可知,创建一个已经存在并且参数都是一样的通道,系统不会执行任何操作,因此在启动应用程序时调用以上代码是安全的。

一旦创建了这个通道,开发者就不能改变这些设置,用户就可以最终控制这些行为。

现在,看一看我们的应用的通知设置,可以看到有一个通知通道

1540111179835

并且还可以设置其重要性、声音以及振动之类的。

1540111153406

准备工作

首先,需要检查NotificationCompat的依赖

1
2
3
dependencies {
implementation "com.android.support:support-compat:28.0.0"
}

发送基本通知

这里使用NotificationCompat来创建一个基本的通知,然后使用notificationManager发送通知

1
2
3
4
5
6
7
8
9
10
11
12
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, NotifyManger.CHANNEL_ID)
.setSmallIcon(R.mipmap.empty_star) // 设置小图标
.setLargeIcon(NotifyManger.getNotifyBitmap(context, R.drawable.notify_icon))
.setContentTitle("今日推荐(⊙o⊙)?") // 设置内容标题
.setTicker("今日推荐")
.setAutoCancel(true) // 点击后是否自动删除
.setContentText(foodList.get(randFood).getFoodName()) // 设置内容
.setContentIntent(resultPendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH); // 设置优先级
NotifyManger.createNotificationChannel(context);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(100, mBuilder.build());

然后我们来创建一个意图,用于表明当用户点击通知的时候的操作。

1
2
3
4
Intent resultIntent = new Intent(context, FoodDetailActivity.class);
resultIntent.putExtra("pos", randFood);
resultIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent resultPendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);

addFlags的方法可以指定开始活动的方法:

  • Intent.FLAG_ACTIVITY_NEW_TASK表明将要启动的Activity放在一个新栈中,对于不是在Activity调用的Intent,如果不加这个参数,就无法启动一个Activity
  • Intent.FLAG_ACTIVITY_SINGLE_TOP 表明如果当前栈顶是需要启动的Activity,就不会启动新的Activity,只会调用当前ActivtiyonNewIntent方法。

需要注意的是,PendingIntent的最后一个参数需要为FLAG_UPDATE_CURRENT才能把Intent里面的数据传递过去。

我们也可以使用TaskStackBuilder构建一个栈,表明Activity中的调用序列。

在一开始开发的时候,遇到了下面的问题:

  • 从通知或者小部件跳转无法传递 Intent 中的Extras

参考资料:Intent from notification does not have extras

如果当前Actvity正在运行的时候,传递Intent是不会调用onCreateonNewIntent的,只是被带到前台,因此我们需要设置

1
2
start_test.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_SINGLE_TOP);

还有,在生成PendingIntent的时候,需要指定最后一个参数为FLAG_UPDATE_CURRENT

1
PendingIntent pendIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  • android.app.PendingIntent.FLAG_UPDATE_CURRENT:更新当前PendingIntent的 extra 数据。
  • android.app.PendingIntent.FLAG_CANCEL_CURRENT:取消当前PendingIntent然后产生一个新的。
  • android.app.PendingIntent.FLAG_ONE_SHOTPendingIntent只能使用一次。
  • android.app.PendingIntent.FLAG_NO_CREATE:返回 null。

小部件

参考资料: 官方文档

创建一个App Widget,需要以下的东西:

  • AppWidgetProviderInfo 对象:描述应用部件的元数据,如应用部件的布局文件、更新频率等
  • AppWidgetProvider 类的实现: 定义基于广播事件的与应用部件传递信息的基本方法。 我们可以通过它,控制应用部件更新、启用、禁用和删除。
  • View layout视图布局

Android Studio中,可以通过在右边文件树中右键 - new - Widget - App Widget来创建最基本的以上一系列东西。

声明

首先,需要在Manifest中声明一个Widget

1
2
3
4
5
6
7
8
9
10
<receiver android:name=".widget.MyAppWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="cn.zhenly.experimentone.widget.MyAppWidget" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver>

它指定了Provider所要接收到的Action,以及小部件的一些基本信息。这样,你的小部件就会出现在系统的小部件库当中。

Info

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/my_app_widget"
android:initialLayout="@layout/my_app_widget"
android:minWidth="180dp"
android:minHeight="40dp"
android:previewImage="@drawable/example_appwidget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen|keyguard"></appwidget-provider>

这里可以指定小部件的布局文件以及一些大小

Layout

1
2
3
4
5
6
7
8
9
10
11
<RelativeLayout ...>

<ImageView
android:id="@+id/widget_icon"
.../>

<TextView
android:id="@+id/appwidget_text"
.../>

</RelativeLayout>

这里我使用了RelativeLayout,放置了两个组件。

AppWidgetProvider

这里主要是重写onRecevice方法,定义在接收到广播的时候需要做的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if(Objects.equals(intent.getAction(), WIDGETSTATICACTION)){
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
// 获取Widget的ID
ComponentName thisAppWidget = new ComponentName(context.getPackageName(), MyAppWidget.class.getName());
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);

... // 处理Intent中的数据

// 设置Widget内容
views.setTextViewText(R.id.appwidget_text, “.....”);
// 设置点击事件意图
views.setOnClickPendingIntent(R.id.widget_icon, ...);

appWidgetManager.updateAppWidget(appWidgetIds, views);
}
}

总结

其实上面的内容如果查看官方文档可能会更加详细,这里只是总结了一些比较重要的在一起以及加上了一些自己的理解和坑。如果想要深入了解还是看官方文档吧 🐷

土豪通道
0%