目录
- 简介
- 设计
- 全代码
- activity_main.xml
- activity_login.xml
- BroadCastReceiver.java
- AndroidManifest.xml
- BroadCastReceiver.java
- MainActivity.java
- 运行效果
简介
随着对BroadCast的越来越深入,我们今天要实现一个稍微复杂一点的BroadCast。即我们常用来有时APP打开时如果多个设备同时登录一个帐号,而我们只允许一个设备登录一个帐号时,此时我们的APP会弹一个对话框如:您的账号在别处登录,请重新登陆!。
设计
要制作这样的效果我们依旧是采用BroadCast,而且是一个自定义的Broadcast。此处需要:
1.自定义send一个broadcast;
2.注册一个receiver,使得它监听我们这个自定义的broadcast;
3.在receiver的onReceive事件中,弹出一个“无窗体悬浮alert dialog”;
4.由于Android6及以后的相应权限问题,你还要添加这个无窗体的悬浮alert dialog的权限;
5.又由于我们用的是SDK27及以后版本,因此光添加权限还没有用,还要使用代码唤出android关于这个app的一个“授权”系统窗口,在这个授权窗口内,用户自己点:allow后再进行打开这个app操作,此时这个悬浮alert dialog才能正确被唤起否则当这个alert dialog一旦被唤出你会得到一个permission denied 2038的错误,然后Android App自动退出;
好了,说了这么多我们来看代码
全代码
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:orientation="vertical"> | |
<Button | |
android:id="@+id/buttonLoginInOtherPlace" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="模拟异地登录" /> | |
</LinearLayout> |
activity_login.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:orientation="vertical"> | |
<LinearLayout | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:orientation="horizontal"> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="login_id:" /> | |
<EditText | |
android:width="dp" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" /> | |
</LinearLayout> | |
<LinearLayout | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:orientation="horizontal"> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="password:" /> | |
<EditText | |
android:width="dp" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" /> | |
</LinearLayout> | |
<LinearLayout | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content"> | |
<Button | |
android:id="@+id/buttonLoginSubmit" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="点一下代表登录了" /> | |
</LinearLayout> | |
</LinearLayout> |
以上定义了两个窗体,运行顺序如下:
- activity_main先运行,上面显示一个“模拟异地登录”按钮,点一下会弹出一个alert dialog告诉你你现在要登出;
- 用户点一下这个alert dialog上的【确定】,登录出登录,跳转到一个登录的activity_login界面;
- 在这个activity_login界面直接点【登录】又登进activity_main
先来看我们的Receiver,它接受来自activity_main的【模拟异地登录】按钮发送过来的broad cast。
BroadCastReceiver.java
package org.mk.android.demo.broadcast; | |
import android.app.AlertDialog; | |
import android.content.BroadcastReceiver; | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.Intent; | |
import android.graphics.PixelFormat; | |
import android.os.Build; | |
import android.provider.Settings; | |
import android.util.Log; | |
import android.view.Window; | |
import android.view.WindowManager; | |
public class BroadCastReceiver extends BroadcastReceiver { | |
private final String TAG = "BroadCastWithActivity"; | |
public void onReceive(final Context context, Intent intent) { | |
Log.i(TAG, "receive broadcast->" + intent.getAction()); | |
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); | |
dialogBuilder.setTitle("警告:"); | |
dialogBuilder.setMessage("您的账号在别处登录,请重新登陆!"); | |
dialogBuilder.setCancelable(false); | |
dialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
ActivityCollector.getInstance().finishAll(); | |
Intent intent = new Intent(context, LoginActivity.class); | |
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
context.startActivity(intent); | |
} | |
}); | |
AlertDialog alertDialog = dialogBuilder.create(); | |
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); | |
alertDialog.show(); | |
} | |
} |
接着,我们来看AndroidManifest.xml文件中的注册以及相应的静态权限申请(这个对话框除了静态权限还需要代码在弹出对话框前申请动态权限,这块代码我们写在了MainActivity.java里的)。
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools"> | |
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> | |
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" /> | |
<application | |
android:allowBackup="true" | |
android:dataExtractionRules="@xml/data_extraction_rules" | |
android:fullBackupContent="@xml/backup_rules" | |
android:icon="@mipmap/ic_launcher" | |
android:label="@string/app_name" | |
android:roundIcon="@mipmap/ic_launcher_round" | |
android:supportsRtl="true" | |
android:theme="@style/Theme.DemoBroadCastWithActivity" | |
tools:targetApi=""> | |
<activity | |
android:name=".LoginActivity" | |
android:exported="false"> | |
<meta-data | |
android:name="android.app.lib_name" | |
android:value="" /> | |
</activity> | |
<activity | |
android:name=".MainActivity" | |
android:exported="true"> | |
<intent-filter> | |
<action android:name="android.intent.action.MAIN" /> | |
<category android:name="android.intent.category.LAUNCHER" /> | |
</intent-filter> | |
<meta-data | |
android:name="android.app.lib_name" | |
android:value="" /> | |
</activity> | |
<receiver android:name=".BroadCastReceiver" | |
android:enabled="true" | |
android:exported="true"> | |
<intent-filter> | |
<action android:name="android.intent.action.BOOT_COMPLETED"/> | |
</intent-filter> | |
<intent-filter> | |
<action android:name="ANDROID.INTENT.ACTION.MEDIA_MOUNTED"/> | |
<action android:name="ANDROID.INTENT.ACTION.MEDIA_UNMOUNTED"/> | |
<data android:scheme="file"/> | |
</intent-filter> | |
</receiver> | |
</application> | |
</manifest> |
接着我们来看我们的BroadCastReceiver写法。
BroadCastReceiver.java
package org.mk.android.demo.broadcast; | |
import android.app.AlertDialog; | |
import android.content.BroadcastReceiver; | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.Intent; | |
import android.graphics.PixelFormat; | |
import android.os.Build; | |
import android.provider.Settings; | |
import android.util.Log; | |
import android.view.Window; | |
import android.view.WindowManager; | |
public class BroadCastReceiver extends BroadcastReceiver { | |
private final String TAG = "BroadCastWithActivity"; | |
private static final String BROADCAST_ACTON = "org.mk.android.demo.broadcast"; | |
public void onReceive(final Context context, Intent intent) { | |
if(intent.getAction().equals(BROADCAST_ACTON)) { | |
Log.i(TAG, "receive broadcast->" + intent.getAction()); | |
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); | |
dialogBuilder.setTitle("警告:"); | |
dialogBuilder.setMessage("您的账号在别处登录,请重新登陆!"); | |
dialogBuilder.setCancelable(false); | |
dialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
ActivityCollector.getInstance().finishAll(); | |
Intent intent = new Intent(context, LoginActivity.class); | |
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
context.startActivity(intent); | |
} | |
}); | |
AlertDialog alertDialog = dialogBuilder.create(); | |
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); | |
alertDialog.show(); | |
} | |
} | |
} |
我们可以看到,它里面定义了一个alert dialog,这个dialog在弹出时没有context因此我们把它叫作无activity(窗体)依托dialog,因此这种dialog是必须要申请权限的,这是Android的规定。然后这个alert dialog有一个【确定】按钮,点一下这个【确定】按钮,就会以startActivity的方式再次打开activity_main界面,此处我们需要注意的是,这个startActivity里的intent的类型必须为Intent.FLAG_ACTIVITY_NEW_TASK),否则你死活从这个登录界面跳不回activity_main的界面了。
接着看来MainActivity以及里面发生消息的部分(含代码动态申请Android权限)。
MainActivity.java
package org.mk.android.demo.broadcast; | |
import androidx.appcompat.app.AppCompatActivity; | |
import androidx.localbroadcastmanager.content.LocalBroadcastManager; | |
import android.content.Intent; | |
import android.content.IntentFilter; | |
import android.os.Bundle; | |
import android.provider.Settings; | |
import android.util.Log; | |
import android.view.View; | |
import android.widget.Button; | |
public class MainActivity extends BaseActivity { | |
private BroadCastReceiver localReceiver; | |
private LocalBroadcastManager localBroadcastManager; | |
private IntentFilter intentFilter; | |
private static final String BROADCAST_ACTON = "org.mk.android.demo.broadcast"; | |
private final String TAG = "BroadCastWithActivity"; | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
Button buttonLoginInOtherPlace = (Button) findViewById(R.id.buttonLoginInOtherPlace); | |
localBroadcastManager = LocalBroadcastManager.getInstance(this); | |
//初始化广播接收者,设置过滤器 | |
localReceiver = new BroadCastReceiver(); | |
intentFilter = new IntentFilter(); | |
intentFilter.addAction(BROADCAST_ACTON); | |
localBroadcastManager.registerReceiver(localReceiver, intentFilter); | |
buttonLoginInOtherPlace.setOnClickListener(new View.OnClickListener() { | |
public void onClick(View view) { | |
if (!Settings.canDrawOverlays(MainActivity.this)) { | |
Intent mintent = new Intent(); | |
mintent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); | |
startActivity(mintent); | |
} | |
Log.i(TAG, ">>>>>>MainActivity->onClick"); | |
Intent intent = new Intent(BROADCAST_ACTON); | |
localBroadcastManager.sendBroadcast(intent); | |
} | |
}); | |
} | |
protected void onDestroy() { | |
super.onDestroy(); | |
localBroadcastManager.unregisterReceiver(localReceiver); | |
} | |
} |
这边核心注意点:
代码动代申请权限
if (!Settings.canDrawOverlays(MainActivity.this)) { | |
Intent mintent = new Intent(); | |
mintent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); | |
startActivity(mintent); | |
} |
代码运行到这,Android会打开一个该APP相应的系统权限对话框
然后把这边的Allow手动启用。
接着我们重新运行这个APP,效果如下。
运行效果
点击【确定】登出activity_main切换到activity_login
在这个界面点击【点一下代表登录了】按钮,再次回到activity_main