Android MVP模式下的全局监听网络状态优化
刚接触安卓的时候是最普通的MVC模式,换了一家公司后认识到MVP模式的重要性(面试别的公司没有接触过MVP模式被嫌弃o(╥﹏╥)o)正好在新的公司练练手,下面进入正题(MVP模式小白,大神勿喷!):
首先这个是我自己设计的一些包便于管理;
之前我的监听网络状态变化是在BookActivity这个View中,但是我意识到如果每一次我进入一个活动页面都要写一次监听方法就违背了MVP的模式,也不能达到代码复用的问题,而且最严重的问题是当我从BookActivity跳转到测试的UiTestActivity中再按返回键回到BookActivity活动页面,按钮的点击事件反应的特别慢,大约需要1秒钟后才能实现其他功能;
意识到一定是有什么耗时的任务导致了这种情况的发生:
BookActivity 中的onResume()以及onPause()方法原来的代码:
/**
* onCreate与onDestroy
* onResume与onPause
* onStart与onStop
*/
//进入页面,以及从运行后台再次进入活动页面;
@Override
protected void onResume() {
super.onResume();
if (netBroadcastReceiver == null) {
Log.d(TAG, "netBroadcastReceiver进入 ");
//实例化网络接收器
netBroadcastReceiver = new NetBroadcastReceiver();
//实例化意图
IntentFilter filter = new IntentFilter();
//设置广播的类型
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
//注册广播,有网络变化的时候会触发onReceive
registerReceiver(netBroadcastReceiver, filter);
// 设置监听
netBroadcastReceiver.setNetEvent((NetEvent) getNetEventActivity());
}else{
Log.d(TAG, "netBroadcastReceiver没有进入 ");
}
}
/**
* 在页面销毁的时候,取消注册的广播
*/
@Override
protected void onPause() {
super.onPause();
if (netBroadcastReceiver != null) {
Log.d(TAG, "netBroadcastReceiver 不为null ");
unregisterReceiver(netBroadcastReceiver);
netBroadcastReceiver = null;
}else{
Log.d(TAG, "netBroadcastReceiver 为null ");
}
}
注释掉onResume()以及onPause()里面的代码后确实恢复了按钮的正常反应速度;
于是进行代码的改进:
查阅了一上午的各种资料,最后发现
1,可以再BaseActivity中实现自定义的网络变化接口,但是会造成registerReceiver()执行两次,不明白什么原因o(╥﹏╥)o,暂时先不考虑了,总之没有用这个方法;
2,可以在Application中进行注册,百度可以知道Application是一种单利模式,在APP启动的时候调用onCreate()方法,在正在的APP销毁后会调用onTerminate()方法;如果放到里面确实可以达到复用(所有的活动页面只需要实现Application里面的方法就可以了);
下面会放上所有的代码:
自定义的NetBroadcastReceiver :
public class NetBroadcastReceiver extends BroadcastReceiver {
//NetEvent实例;
private NetEvent netEvent;
@Override
public void onReceive(Context context, Intent intent) {
/**
* onReceive这里写上相关的处理代码,一般来说,不要此添加过多的逻辑或者是进行任何的耗时操作
* 因为广播接收器中是不允许开启多线程的,过久的操作就会出现报错
* 广播接收器更多的是扮演一种打开程序其他组件的角色,比如创建一条状态栏通知,或者启动某个服务
*/
//判断广播的类型为网络action后
if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
Log.d("MyApplication.cl", "myListenefdfa");
//获取当前网络状态,并将结果发送给广播接受者
int netWrokState = NetUtil.getNetWorkState(context);
if (netEvent != null){
Log.d("MyApplication.cl", "myListenfffff");
//调用接口里面的网络变化方法;
netEvent.onNetChange(netWrokState);
}else{
Log.d("MyApplication.cl", "myListeeeeeea");
}
}
}
/**
* 从外部获取到NetEvent接口;
* @param netEvent
*/
public void setNetEvent(NetEvent netEvent) {
this.netEvent = netEvent;
}
}
自定义的接口 NetEvent ;
public interface NetEvent {
//监听网络变化的状态;
void onNetChange(int netMobile);
}
NetUtil 类 用于判断网络状态的变化的方法等
public class NetUtil {
//定义三种网络状态|| -1:没有网络 /0:移动网络 /1:wifi网络
private static final int NETWORK_NONE = -1;
private static final int NETWORK_MOBILE = 0;
private static final int NETWORK_WIFI = 1;
public static int getNetWorkState(Context context) {
//获取连接管理器对象
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//获取额外的网络信息
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
//判断网络处于连接状态
if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
//获取网络的类型 wifi 还是 network
if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) {
Log.i("通知" , "当前网络处于WiFi状态");
//是否可上网
if (isNetworkOnline()){
return NETWORK_WIFI;
}else{
return NETWORK_NONE;
}
} else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) {
Log.i("通知" , "当前网络处于移动网络状态");
return NETWORK_MOBILE;
}
} else {
return NETWORK_NONE;
}
return NETWORK_NONE;
}
//判断当前WIFI网络是否能连同外网 通过ping的方式进行;
public static boolean isNetworkOnline() {
Runtime runtime = Runtime.getRuntime();
try {
Process ipProcess = runtime.exec("ping -c 3 www.baidu.com");
int exitValue = ipProcess.waitFor();
Log.i("Avalible", "Process:"+exitValue);
//wifi不可用或未连接,返回2;WiFi需要认证,返回1;WiFi可用,返回0;
return (exitValue == 0);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return false;
}
}
自定义的MyApplication ,因为需要分包,所以继承了MultiDexApplication ,没有分包的可以直接继承Application ;另外代码中的注释我懒得整理了 ,看没有注释掉的代码就OK;
public class MyApplication extends MultiDexApplication {
/**
* 1.Activity.this 的context
* (一般用法)返回当前activity的上下文,属于activity ,zactivity 摧毁他就摧毁
* 2.getApplicationContext()
* 返回应用的上下文,生命周期是整个应用,应用摧毁它才摧毁
* 3.getBaseContext()
* 返回由构造函数指定或setBaseContext()设置的上下文
* 4.getActivity()
* 多用于fragment中
*/
//自定义的可以在全局用到的context;方便在各个自定义类中进行使用;
private static Context context;
private static final String TAG ="MyApplication.cl";
//自定义的广播接收器
private NetBroadcastReceiver netStateReceiver;
@Override
public void onCreate() {
super.onCreate();
/* //通过getApplicationContext() 返回应用的上下文,生命周期是整个应用;
context = getApplicationContext();
//QMUI --arch库提供的 QMUIFragment、QMUIFragmentActivity、QMUIActivity 来作为基础类构建自己的界面
//必须在这里初始化,不然的话会出现闪退的情况;
QMUISwipeBackActivityManager.init(this);
//初始化Bugly
initBugly();*/
//注册网络变化
registerReceiver();
}
@Override
public void onTerminate() {
// 程序终止的时候执行
unregisterReceiver();
super.onTerminate();
}
/**
* 初始化Bugly,并且设置上报主线程的错误;为了节省流量、内存等资源,建议初始化的时候对上报进程进行控制,只在主进程下上报数据
*/
private void initBugly(){
/* Context context = getApplicationContext();
// 获取当前包名
String packageName = context.getPackageName();
// 获取当前进程名
String processName = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
processName = getProcessName(android.os.Process.myPid());
}
// 设置是否为上报进程
CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(context);
strategy.setUploadProcess(processName == null || processName.equals(packageName));
//Bugly 日志接入配置 这是最简单的初始化
//CrashReport.initCrashReport(getApplicationContext(), "1062abe6d6", false);
//另外如果在AndroidManifest.xml中进行配置;则用下面的这个;
//CrashReport.initCrashReport(getApplicationContext());
CrashReport.initCrashReport(context, strategy);*/
}
/**
* 获取进程号对应的进程名
* 服务于上面获取进程的报名
* @param pid 进程号
* @return 进程名
*/
private static String getProcessName(int pid) {
/* BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("/proc/" + pid + "/cmdline"));
String processName = reader.readLine();
if (!TextUtils.isEmpty(processName)) {
processName = processName.trim();
}
return processName;
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException exception) {
exception.printStackTrace();
}
}*/
return null;
}
/**
* 注册网络状态监听器(广播接收者)
*/
private void registerReceiver() {
Log.d(TAG, "registerReceiver: ");
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
netStateReceiver = new NetBroadcastReceiver();
this.registerReceiver(netStateReceiver, filter);
}
/**
* 注销网络状态监听器
*/
private void unregisterReceiver() {
//L.w("tan", "unregisterReceiver");
if (netStateReceiver != null) {
this.unregisterReceiver(netStateReceiver);
netStateReceiver = null;
Log.d(TAG, "unregisterReceiver11: ");
}else{
Log.d(TAG, "unregisterReceiver22: ");
}
}
//在需要的Activity中 调用此方法给NetEvent进行赋值,可以达到监听网络变化的得效果
public void setNetStateMyListener(NetEvent myListener) {
if(netStateReceiver!=null){
//传入
netStateReceiver.setNetEvent( myListener);
Log.d(TAG, "myListener:"+myListener);
}else{
unregisterReceiver();
Log.d(TAG, "setNetStateMyListener22: ");
}
}
/**
* 获取全局上下文
*/
public static Context getContext() {
return context;
}
}
在测试的BookActivity中调用就可以了
public class BookActivity extends BaseMvpActivity<BookView, BookPresenter> implements BookView {
@BindView(R.id.button)
Button button;
@BindView(R.id.text)
TextView text;
@BindView(R.id.net_warning_img)
ImageView netWarningImg;
@BindView(R.id.net_warning_text)
TextView netWarningText;
@BindView(R.id.net_warning)
LinearLayout netWarning;
@BindView(R.id.button1)
Button button1;
@BindView(R.id.button2)
Button button2;
@BindView(R.id.button3)
Button button3;
@BindView(R.id.button4)
Button button4;
@BindView(R.id.button5)
QMUIRoundButton button5;
//跳转页面在加载数据过程中展示的进度条;
private ProgressDialog progressDialog;
private static final String TAG = "BookActivity.cl";
//用于接受网络状态改变的三种状态 -1不可用,0 移动网络,1 wifi
private int netMobile;
/**
* @return 此活动页面的布局ID;
*/
@Override
protected int getLayoutId() {
return R.layout.activity_book;
}
// 获取presenter实例
@Override
protected BookPresenter createPresenter() {
return new BookPresenter();
}
// 初始化界面
@Override
protected void initViews() {
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage("正在拼命加载中...");
}
// 按钮的点击事件:请求接口,获取网络数据
public void getData(View view) {
// 调用presenter层的方法执行网络请求
try {
getPresenter().getSearchBooks("11", "22");
} catch (Exception e) {
Log.d("22ss", e.toString());
}
}
//实现BookView接口中的访问数据成功接口
@Override
public void onSuccess(Book mBook) {
// 将获取到的数据的信息显示在TextView组件上
try {
text.setText(mBook.getCount() + "");
} catch (Exception e) {
Log.d("22ss", e.toString());
}
}
// 或者通过设置progress组件的VISIBLE和GONE来控制进度提示的显示和隐藏,此处是使用对话框提示
@Override
public void showProgressDialog() {
if (!progressDialog.isShowing()) {
progressDialog.show();
}
}
@Override
public void hideProgressDialog() {
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
@Override
public void onError(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: add setContentView(...) invocation
ButterKnife.bind(this);
}
/**
* onCreate与onDestroy
* onResume与onPause
* onStart与onStop
*/
//进入页面,以及从运行后台再次进入活动页面;
@Override
protected void onResume() {
super.onResume();
//设置开始监听网络变化
((MyApplication)getApplication()).setNetStateMyListener(this);
}
@Override
public void onNetChange(int netMobile) {
this.netMobile = netMobile;
isNetConnect();
}
/**
* 在页面销毁的时候,取消注册的广播
*/
@Override
protected void onPause() {
super.onPause();
//设置结束网络变化监听
((MyApplication)getApplication()).setNetStateMyListener(null);
}
//根据返回来的数字进行判断网络状态;
private void isNetConnect() {
switch (netMobile) {
case 1:// wifi
netWarning.setVisibility(View.GONE);
break;
case 0:// 移动数据
netWarning.setVisibility(View.GONE);
break;
case -1:// 没有网络
netWarning.setVisibility(View.VISIBLE);
break;
}
}
@OnClick({R.id.button, R.id.button1, R.id.button2, R.id.button3, R.id.button4,R.id.button5})
public void onViewClicked(View v) {
switch (v.getId()) {
case R.id.button:
getData(v);
break;
case R.id.button1:
MyToast.showNormalToast(this, "1");
break;
case R.id.button2:
MyToast.showNormalWithTimeToast(this, "11", 3000);
break;
case R.id.button3:
MyToast.showWithPicToast(this, "2", true);
break;
case R.id.button4:
startActivity(new Intent(this,UiTestActivity.class));
break;
case R.id.button5:
startActivity(new Intent(this,UiTestActivity.class));
break;
}
}
/**
* 在Baseactivity中定义了方法需要把活动页面传入进去;然后在NetWrok 广播中需要;
*
* @return
*/
/**
* 监听返回按钮的点击事件;
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
Log.d(TAG, "onKeyDown: ");
break;
default:
Log.d(TAG, "otherKeyDown: ");
}
return super.onKeyDown(keyCode, event);
}
}
上面的BookActivity中的代码没有的比较多,弄一个图片吧!
在需要调用网络监听的活动页面的onResume()方法里调用setNetStateMyListener方法,传入活动页面;
在onPause()方法里调用setNetStateMyListener方法,传入null;
我自己自定义的一个BaseActivity 实现了NetEvent接口:
public abstract class BaseActivity extends AppCompatActivity implements NetEvent{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置活动页面的布局;
setContentView(getLayoutId());
//实例化MVP中的P用来连接控制Model与View;
initPresenter();
//初始化控件,一般在BaseActivity中通过ButterKnife来绑定,所以该方法内部一般我们初始化界面相关的操作
initViews();
//获取数据
getDataFromServer();
}
/**
* onCreate与onDestroy
* onResume与onPause
* onStart与onStop
*/
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
// 设置布局
protected abstract int getLayoutId();
// 初始化界面
protected abstract void initViews();
// 获取数据
protected void getDataFromServer(){}
// 实例化presenter
protected void initPresenter(){}
}
在调用的活动页面实现NetEvent接口中的onNetChange()方法:
至此,代码优化了,符合MVP设计思想了,我尝试着运行,打印了一些信息没有遇到什么问题,如果有大神知道我的代码哪里是有问题的,感谢您抽时间给我留言 指点我一下;谢谢!!!