之前工作主要集中在Activity和fragment中,广播、内容提供者、服务接触很少,竟然有点想不起来怎么写了,故又看了一遍,先记录一篇广播的吧。
类似于微信、QQ等大型app,毫无疑问都有强制下线(即在别处登录时当前登录马上接收到下线通知),一般来说,实现过程是在A手机登录时,记录设备唯一标识,当在B登录时,后台检验上传唯一标识是不是跟后台记录的相同,此时显然是不同的(手机不同),修改当前唯一标识,给A手机推送下线通知。但是只是为了学习下广播,就不搞那么复杂了,直接用点击模拟效果来实现吧。
先上一张效果图吧(不要在意样式,先实现功能吧。手动滑稽):
首先,如果接收到强制下线请求,应该把当前的活动全部关闭,然后跳转到登录页面,基本逻辑就是这样。知道逻辑就该考虑应该怎么做?关闭所有活动的话直接建一个管理活动的类即可,在baseActivity中添加删除即可。ok,接下来问题来了,接收到下线通知时怎么在其他页面完成把当前的活动全部关闭跳转到登录页面呢?难道每一个页面都进行一层判断吗?显然不现实。对了,就是使用广播,咱们还有BaseActivity。在接到下线通知时,发送一个广播,在BaseActivity中注册接收广播,如果接收到下线通知,直接弹出对话框,强制用户下线。
ok,基本逻辑和解题思路都出来了,接下来就该编写代码了,首先写一个简单的活动管理类,用来管理所有的活动:
/** * 活动管理类 * * @author jiang zhu on 2019/9/26 */ @SuppressLint("Registered") public class ActivityController extends Application { //静态单例 @SuppressLint("StaticFieldLeak") private static ActivityController instances; //新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 //随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针 public LinkedList<Activity> activityLinkedList = new LinkedList<>(); private ActivityController() { } /** * 获取单例 * @return / */ public static ActivityController getInstances() { if (instances == null) { synchronized (ActivityController.class) { if (instances == null) { instances = new ActivityController(); } } } return instances; } /** * 将活动添加至栈 * * @param activity 活动 */ public void addActivity(Activity activity) { activityLinkedList.add(activity); } /** * 将活动从栈中移除 * * @param activity 活动 */ public void removeActivity(Activity activity) { activityLinkedList.remove(activity); } /** * 将所有类退出栈 */ public void exit() { try { for (int i = 0; i < activityLinkedList.size(); i++) { activityLinkedList.get(i).finish(); } } catch (Exception e) { System.exit(0); e.printStackTrace(); } } }
好的,我们先来看一下活动管理类的逻辑,活动管理类,一个进程只能有一个,反之,强制下线就关闭不了所有的活动,所以我写成了单例模式。管理活动的集合我使用了LinkedList,LinkedList在新增和删除操作占优势,因为ArrayList需要移动数据。由于每个活动都需要添加和删除,而退出基本很少使用,所以我使用了LinkedList。下面一个添加,一个移除,最后一个将所有活动finish掉。
活动管理类有了,但是活动还没有。好,接下来开始编写一个BaseActivity,并添加上简单逻辑。
/** * Activity父类 * * @author jiang zhu on 2019/9/26 */ public abstract class BaseActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getContentView()); ActivityController.getInstances().addActivity(this); initView(); } /** * 获取当前活动的view * * @return view */ protected abstract int getContentView(); /** * 初始化view */ protected abstract void initView(); @Override protected void onPause() { super.onPause(); if (newWorkBroadcast != null) { unregisterReceiver(newWorkBroadcast); } } @Override protected void onDestroy() { super.onDestroy(); ActivityController.getInstances().removeActivity(this); } }
这个代码就很简单了,首先是一个抽象类继承自Activity。然后把设置布局和加载布局当作抽象方法抽出来,并在onCreated()中将此活动添加在活动管理类中,在onDestory()中移除掉。
接下来,该去接收强制下线通知了。咱们再理一下逻辑:进入应用首先进入登录页面,登录成功之后进入主页面,主页面放一个按钮,点击模拟发出强制下线的广播,然后BaseActivity中接收广播,弹出对话框。
一步一步来,先创建一个登录页面,先贴一下布局文件吧:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".LoginActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="25sp" android:layout_gravity="center" android:layout_marginTop="80dp" android:text="登录"/> <EditText android:id="@+id/loginEtAccount" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:layout_marginTop="50dp" android:hint="请输入账号"/> <EditText android:id="@+id/loginEtPassword" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:hint="请输入密码"/> <Button android:id="@+id/loginBtnLogin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="10dp" android:text="登录"/> </LinearLayout>
显示效果如下:
接下来是活动代码,里面有简单的登录逻辑:
/** * 登录 */ public class LoginActivity extends BaseActivity implements View.OnClickListener { private EditText loginEtAccount; private EditText loginEtPassword; @Override protected int getContentView() { return R.layout.activity_login; } @Override protected void initView() { loginEtAccount = findViewById(R.id.loginEtAccount); loginEtAccount.setOnClickListener(this); loginEtPassword = findViewById(R.id.loginEtPassword); loginEtPassword.setOnClickListener(this); Button loginBtnLogin = findViewById(R.id.loginBtnLogin); loginBtnLogin.setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.loginBtnLogin) { submit(); } } /** * 登录 */ private void submit() { // validate String loginEtAccountString = loginEtAccount.getText().toString().trim(); if (TextUtils.isEmpty(loginEtAccountString)) { Toast.makeText(this, "请输入账号", Toast.LENGTH_SHORT).show(); return; } String loginEtPasswordString = loginEtPassword.getText().toString().trim(); if (TextUtils.isEmpty(loginEtPasswordString)) { Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show(); return; } if ("123".equals(loginEtAccountString)&&"123".equals(loginEtPasswordString)){ startActivity(new Intent(this,MainActivity.class)); }else { Toast.makeText(this, "账号或密码输入错误", Toast.LENGTH_SHORT).show(); } } }
咱们来看一下这个类,首先继承自BaseActivity,然后直接登录,如果账号密码都是123时登录成功。成功后跳转到主页面,接下来就该进入广播了,写了半天没用的代码。。。
还是先放下布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="发送强制下线广播" android:layout_marginTop="50dp" android:layout_gravity="center" tools:ignore="HardcodedText" /> </LinearLayout>
布局太简单就不放图了,只是一个按钮。下面是主页面的活动。
/** * 主程序 */ public class MainActivity extends BaseActivity implements View.OnClickListener { @Override protected int getContentView() { return R.layout.activity_main; } @Override protected void initView() { Button button = findViewById(R.id.button); button.setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.button) { Intent intent = new Intent(FORCE_OFFLINE); sendBroadcast(intent); } } }
与上相同,主页活动也继承自BaseActivity,当点击按钮时发出一条广播。大家可能会报错,FORCE_OFFLINE应该是找不到,这只是自定义的一个字符串,表示广播的唯一性。在下面会写出来的。
广播也发出了,最后只剩下拦截广播并且弹出对话框强制下线了。说干就干:
/** * Activity父类 * * @author jiang zhu on 2019/9/26 */ public abstract class BaseActivity extends Activity { public static final String FORCE_OFFLINE= "zj.it.bhne.broadcast.FORCE_OFFLINE"; private ForceOfflineBroadcast newWorkBroadcast; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getContentView()); ActivityController.getInstances().addActivity(this); initView(); } /** * 获取当前活动的view * * @return view */ protected abstract int getContentView(); /** * 初始化view */ protected abstract void initView(); /** * 强制下线功能 */ @Override protected void onResume() { super.onResume(); //接收强制下线的广播 IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(FORCE_OFFLINE); newWorkBroadcast = new ForceOfflineBroadcast(); registerReceiver(newWorkBroadcast, intentFilter); } @Override protected void onPause() { super.onPause(); if (newWorkBroadcast != null) { unregisterReceiver(newWorkBroadcast); } } @Override protected void onDestroy() { super.onDestroy(); ActivityController.getInstances().removeActivity(this); } public class ForceOfflineBroadcast extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle("强制下线"); builder.setMessage("在别处登录,需要强制下线"); builder.setCancelable(false); builder.setPositiveButton("下线", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityController.getInstances().exit(); dialog.dismiss(); startActivity(new Intent(context, LoginActivity.class)); } }); builder.show(); } } }
还是刚才的BaseActivity,只是在里面加入了其他代码。咱们来慢慢看:写上之后你会发现刚才报的错解决了。直接来强制下线的功能,在注释中也写好了,大家可以看到我写在了onResume()中,这是因为在onResume()执行时用户才真正可见并可以进行操作,声明一个IntentFilter,在里面addAction,放入刚才发出的广播,FORCR_OFFLINE只是一个字符串,定义成什么都可以,建议以包名+功能来命名。然后创建一个广播接收者,注册接收咱们发出的广播。
广播接收者的代码不多,一行一行看,首先继承自BroadcastReceiver,它也是一个抽象类,需要实现其中的抽象方法onReceive(),在其中直接弹出对话框,使之不能操作,只能点击按钮退出到登陆页面。
最后在清单文件中注册一下,四大组件嘛。
<receiver android:name=".BaseActivity$ForceOfflineBroadcast" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="zj.it.bhne.broadcast.MY_BROADCAST" /> </intent-filter> </receiver>
好的,到此基本上代码就编写完了。