Android开发——1套灵活收发广播组合拳,打遍应用层所有

为何要收藏这篇干货?——番茄憨憨的自我独白

我是番茄憨憨,我要坦白。

伴随着Android版本升级,某些静态广播过滤已受到限制,而动态广播又完全依赖于应用的启动,这就导致在实现一些需求时显得比较呆板,如:
全局监听无探网的WiFi时,将给到用户一个友好的Dialog提示。

针对于这个需求,如果只是在应用层添加广播,如果此时应用不启动,又检测到了此时连接的wifi为无探网,显然友好的dialog并不会弹出,这很不友好.

063993e59dfddbe27b2228ee9f6175f7.jpg

那么到底该怎么做呢?一套灵活收发广播组合拳带给大家,打遍应用层所有dialog开发需求.

先上结论:

    1. framework层系统服务中添加广播
    1. 应用AndroidManifest.xml注册广播接收器
    1. 实现透明主题Activity及dialog

注:关于正文的分析我会围绕为什么、怎么做去进行,而大家的思考可以反其道而行之,从他是怎么做的、为什么这样做、做的正确吗?有更好的方案欢迎大家在下方评论点评,让我们一起进步!

1、framework层系统服务中添加广播

1.1. 为什么要在fraemwork层系统服务中添加广播?

  如上面独白中所说,只是在应用层添加广播,静态广播存在局限性,有些IntentFilter存在限制,而动态广播应用没起来的情况下,是不生效的,无法实现一个全局监听数据变化的效果,而在框架中的系统服务中添加广播,因系统服务依赖于系统,即可做到一个实时监听的效果.

1.2. 相关代码添加

a.在服务中注册广播(某些服务中已经注册了广播,我们可以直接添加过滤器即可,以AudioService为例,这里将演示完整的注册代码)

1
2
3
4
java复制代码/*
* 1、定义AudioService的广播
*/
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
scss复制代码/*
* 2、IntentFilter的添加和广播注册封装成一个方法
*/
private void initExternalEventReceivers() {
mSettingsObserver = new SettingsObserver();

// 可以看到下面添加了一堆过滤条件
IntentFilter intentFilter =
new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
getAudioServiceExtInstance().getBleIntentFilters(intentFilter);
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
//由于是在系统服务里面,所以调用的广播注册接口传是需要传更多参数的.
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
Context.RECEIVER_EXPORTED);
SubscriptionManager subscriptionManager = mContext.getSystemService(
SubscriptionManager.class);
if (subscriptionManager == null) {
Log.e(TAG, "initExternalEventReceivers cannot create SubscriptionManager!");
} else {
subscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
}
}
1
2
3
4
arduino复制代码/*
* 3、对initExternalEventReceivers方法进行调用
*/
未贴代码,随便找个初始化的地方调用即可.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
scala复制代码 /*
* 4、对AudioServiceBroadcastReceiver增加逻辑处理,因为前面已经添加了过滤器,所以可以在自己的过滤器中做自己想做的事情,我们在这里要做的,就是发送一个自定义的广播出去,绕开某些静态广播存在限制导致接收的问题.
*/
private class AudioServiceBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)){
sendWifiNoInternetBroadcasts(nai);
}
}

//自定义发送的广播
private void sendWifiNoInternetBroadcasts(NetworkAgentInfo nai){
Slog.d(TAG, "sendWifiNoInternetBroadcasts");
final Intent wifiNoIntent = new Intent("android.tinno.action.WIFI_NO_INTERNET_ACTION")
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
if (nai != null){
String ssid = WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid());
Slog.d(TAG, "sendWifiNoInternetBroadcasts :: ssid == " + ssid);
if (ssid != null){
wifiNoIntent.putExtra("ssid",ssid);
}
}
mContext.sendBroadcast(wifiNoIntent);
}

2、应用AndroidManifest.xml注册广播接收器

2.1 广播为什么还注册在应用的AndroidManifest里面?

  可以注意到,我们上面发送的广播的IntentFilter是android.tinno.action.WIFI_NO_INTERNET_ACTION,过滤器是自定义的,所以这是我们再去在应用中注册静态广播,只要自定义的广播发出来,是一定能够接收到的,说到底还是为了饶开某些Android原生过滤条件无法在静态广播中使用的情况,相关代码如下:

1
2
3
4
5
6
7
8
9
xml复制代码<!--在AndroidManifest.xml注册静态广播-->
<receiver
android:name="com.qualcomm.qti.settings.watchwifi.WifiNoInterNetReceiver"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="android.tinno.action.WIFI_NO_INTERNET_ACTION" />
</intent-filter>
</receiver>

2.2 如此大费周章的接收到广播,此广播究竟干什么?

  回到我们的需求,我们是要实现一个Dialog,而Dialog是依附于Activity的,那依附于哪个Activity呢?没错,我们此广播做的唯一一件事,就是去指定并启动这个Activity,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
scala复制代码package com.qualcomm.qti.settings.watchwifi;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.provider.Settings;

import com.qualcomm.qti.settings.watchwifi.WifiNoInterNetActivity;

import android.net.wifi.WifiManager;

/**
* @Descpition: 只负责接受框架层发送的wifi无网广播
**/
public class WifiNoInterNetReceiver extends BroadcastReceiver {
private static final String TAG = "WifiNoInterNet";

@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "WifiNoInterNetReceiver :: onReceive");
intent.setClass(context, WifiNoInterNetActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
//启动Activity!
context.startActivity(intent);
}
}

其实到这里,这套灵活的广播组合拳已经打完,其目的就是确保我们在应用中注册的静态广播是一定能够收到相关消息,现在我们来看最后一个小知识点——透明主题.

3、实现透明主题Activity及dialog

3.1 为什么需要透明主题的Activity?

  我们的目的只是为了显示一个Dialog,而不是为了显示一个Activity,所以为了视觉上的呈现效果(给用户营造dialog就在当前的页面上显示的),我们就需要透明主题的实现,关于透明主题没啥好说的,咱直接上代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
typescript复制代码package com.qualcomm.qti.settings.watchwifi;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import android.view.Gravity;
import android.os.Handler;
import android.os.Looper;
import android.widget.TextView;

/**
* @Descpition: 透明主题,只显示一个view,1s后自动销毁.
* @Author wpc
* @Date 2024/4/23 13:52
**/
public class WifiNoInterNetActivity extends Activity{
public static final String TAG = "WifiNoInterNet";
private Handler mHandler;
private TextView textView;

private String ssid;
private Intent intent;
//可以看到,onCreate里面设置了1.5s后此页面会被销毁.
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG,"WifiNoInterNetActivity :: onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.no_internet_dialog);
if (intent == null) {
intent = getIntent();
}
if (intent.hasExtra("ssid")){
ssid = (String)intent.getExtra("ssid");
Log.d(TAG, "WifiNoInterNetActivity :: intent.hasExtra(\"ssid\") && ssid == " + ssid);
}
String targetSsid = ssid + " " +"has no internet access.";
textView = findViewById(R.id.wifi_no_internet_tv);
textView.setText(targetSsid);
mHandler = new Handler(Looper.getMainLooper());
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
textView = null;
finish();
}
}, 1500);

}
}
1
2
3
4
5
6
7
xml复制代码    <!--透明主题的Style,直接在style文件中添加这一段,然后直接在Activity注册的地方引用此主题样式即可-->
<style name="TransparentActivityTheme">
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
</style>
1
2
3
4
5
6
xml复制代码    <!--activity的注册,在这里引用其透明主题,不要在activity的具体布局中去引用-->
<activity
android:name="com.qualcomm.qti.settings.watchwifi.WifiNoInterNetActivity"
android:theme="@style/TransparentActivityTheme"
android:excludeFromRecents="true"
android:launchMode="singleInstance" />

至此,一套灵活收发广播组合拳,打遍应用层所有Dialog开发需求核心内容就已经搞定,此方案具备很大的灵活性,针对于应用层开发来说,能够接收到自己在框架层定义发送的广播,其实已经完成了一个收发闭环。

当然,解决问题的方式有很多种,这不会是唯一一种,也绝不是最好的一种,欢迎大家关注、收藏,转发,最后再评论,我是番茄憨憨,哈哈哈哈哈!!!

u=4130461739,665980987&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto.webp

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%