文章目录
PS :
平时遇到问题到网上搜索博客,作者总是先列出方案1,方案2,方案3, 最后才把最优的解决方式放到最后.
遇到这种情况我总是很焦急, 我心说: “我这得赶紧解决问题,哪有空看你分析啊”, 后来倒是明白了,把自己解决问题的思路写下来,实际上是学习了这种思维方式. 所以我也准备里这么做.
PS: 着急的可以直接看最后的解决方案.
问题:
场景:
当一个界面同一时间有七八个甚至十几个网络请求,这个时候对话框是如何展示的. 我的项目中效果就是对话框只显示一次就消失了. 因为我采取的方案是如果当对话框有并发创建的时候,会先去判断对话框是否显示中,如果显示中就关闭.
具体代码如下:
整体代码
public class LoadingFragment {
private static WeakReference<Context> contextWeakReference;
private String content;
private static Dialog loadingDialog;
private static AnimationDrawable animationDrawable;
private static ImageView carImg;
private LoadingFragment() {
}
public static Dialog showLodingDialog(Context context) {
return showLodingDialog(context, true);
}
/**
* @param context
* @param mIsCancel true 返回键不取消 false 返回键取消
* @return
*/
public static Dialog showLodingDialog(Context context, boolean mIsCancel) {
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.progress_hud_2, null);// 得到加载view
carImg = v.findViewById(R.id.spinnerImageView);
TextView tvContent = v.findViewById(R.id.message);
Animation rotateAnimation = AnimationUtils.loadAnimation(context, R.anim.rotate_anim);
LinearInterpolator lin = new LinearInterpolator();
rotateAnimation.setInterpolator(lin);
carImg.startAnimation(rotateAnimation);
tvContent.setText("加载中...");
if (null != loadingDialog && loadingDialog.isShowing()) {
loadingDialog.dismiss();
}
loadingDialog = new Dialog(context, R.style.ProgressHUD);// 创建自定义样式dialog
loadingDialog.setCancelable(true); // 是否可以按“返回键”消失
loadingDialog.setCanceledOnTouchOutside(false); // 点击加载框以外的区域
loadingDialog.setContentView(v, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));// 设置布局
if (mIsCancel)//点击返回键不取消
loadingDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
return true;
} else {
return false;
}
}
});
//将显示Dialog的方法封装在这里面
Window window = loadingDialog.getWindow();
if (window != null) {
window.clearFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND | WindowManager.LayoutParams.FLAG_DIM_BEHIND);
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setGravity(Gravity.CENTER);
window.setAttributes(lp);
}
// window.setWindowAnimations(R.style.PopWindowAnimStyle);
if (context.getClass().getName().contains("Activity") && !((Activity) context).isFinishing()) {
loadingDialog.show();
} else {
LogUtil.d("展示对话框前先消失");
dismiss();
}
return loadingDialog;
}
/**
* 关闭dialog
*/
public static void dismiss() {
if (null != loadingDialog && loadingDialog.isShowing()) {
loadingDialog.dismiss();
loadingDialog = null;
}
if (null != carImg) {
carImg.clearAnimation();
}
}
}
可以看见关键的代码在这里
if (null != loadingDialog && loadingDialog.isShowing()) {
loadingDialog.dismiss();
}
结果就是对话框只展示了一次.那么最终效果肯定不理想.而且如果网络请求没结束,会有很多异常信息.
导致界面显示数据错乱,甚至会crash.那么如何才能让所有网络请求都结束之后对话框才消失?
方案1: 使用rxjava zip 函数
private void RxZip(VipCardInfoBean vipCardInfoBean, PostListener rxPostListener) {
String cardNum = vipCardInfoBean.getEcardNo();
Map<String, String> map = HttpPackageParams.getCardNumParms(cardNum);
Observable observable1 = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
//券
XHttp.getInstance().post(getActivity(), HttpConfig.MEMBER_COUPONS_COUNT, map, new HttpCallBack<String>() {
@Override
public void onSuccess(String string) {
emitter.onNext(string);
emitter.onComplete();
}
@Override
public void onFailed(int errorCode, String error) {
super.onFailed(errorCode, error);
emitter.onNext("-");
emitter.onComplete();
}
}, false, true);
}
}).subscribeOn(Schedulers.io());
Observable observable2 = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
//进行网路请求
//积分
XHttp.getInstance().post(getActivity(), HttpConfig.MEMBER_INTEGRAL_COUNT, map, new HttpCallBack<String>() {
@Override
public void onSuccess(String string) {
emitter.onNext(string);
emitter.onComplete();
}
@Override
public void onFailed(int errorCode, String error) {
super.onFailed(errorCode, error);
emitter.onNext("-");
emitter.onComplete();
}
}, false, false);
}
}).subscribeOn(Schedulers.io());
Observable observable3 = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
//储值
XHttp.getInstance().post(getActivity(), HttpConfig.FIND_MEMBER_SAVING_COUNT, map, new HttpCallBack<String>() {
@Override
public void onSuccess(String string) {
ElectronicCardBean electronicCardBean = GsonUtil.GsonToBean(string, ElectronicCardBean.class);
if (electronicCardBean != null && !TextUtils.isEmpty(electronicCardBean.getEcardBalance())) {
// updateAdapter(position, "", "", electronicCardBean.getEcardBalance(), true);
emitter.onNext(electronicCardBean.getEcardBalance());
emitter.onComplete();
} else {
emitter.onNext("-");
emitter.onComplete();
}
}
@Override
public void onFailed(int errorCode, String error) {
super.onFailed(errorCode, error);
// updateAdapter(position, "", "", "", false);
emitter.onNext("-");
emitter.onComplete();
}
}, false, false);
}
}).subscribeOn(Schedulers.io());
Observable.zip(observable1, observable2, observable3, new Function3<String, String, String, CardMoneyBean>() {
@Override
public CardMoneyBean apply(String string1, String string2, String string3) throws Exception {
LogUtil.d("优惠券 = " + string1);
LogUtil.d("积分 = " + string2);
LogUtil.d("电子余额 = " + string3);
CardMoneyBean cardMoneyBean = new CardMoneyBean();
cardMoneyBean.setCoupon(string1);
BigDecimal bg = new BigDecimal(string2).setScale(2, BigDecimal.ROUND_HALF_UP);
// 不足两位小数补0
DecimalFormat decimalFormat = new DecimalFormat("0.00#");
String strVal = decimalFormat.format(bg);
vipCardInfoBean.setPoint(strVal);
cardMoneyBean.setPoint(strVal);
cardMoneyBean.setMoney(string3);
return cardMoneyBean;
}
}).subscribe(new Consumer<CardMoneyBean>() {
@Override
public void accept(CardMoneyBean cardMoneyBean1) throws Exception {
LogUtil.d("cardMoneyBean1 = " + GsonUtil.gsonString(cardMoneyBean1));
if ("-".equals(cardMoneyBean1.getMoney())) {
cardMoneyBean1.setHasEcard(false);
} else {
cardMoneyBean1.setHasEcard(true);
}
vipCardInfoBean.setCardMoneyBean(cardMoneyBean1);
postCount++;
if (postCount == list.size()) {
if (rxPostListener != null)
rxPostListener.onFinish();
}
}
@Override
protected void finalize() throws Throwable {
LogUtil.d("finalize");
LoadingFragment.dismiss();
super.finalize();
}
});
}
在这里我只让第一个请求弹出对话框 . 这样就可以形成三个网络请求只显示一个对话框
方案2: 使用java future 类
方案3: 使用计数器
方案3 优化 单例(有异常情况)
思想: 使用一个变量用户存储当前展示的对话框,当有一个网络请求进入的时候对这个变量进行加一(如果变量大于0 就不创建对话框), 在关闭对话框的时候进行判断 如果这个变量大于0 那么说明整个网络请求未结束.不关闭这个对话框,并且对这个变量减一. 如果变量小于=0 则把对话框关闭. ----- (存在多线程并发问题)
2021.2.1 之前曾经尝试使用volatile并不管用,
顺带说下volatile关键字很重要的两个特性:
1、保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—“先行发生原则”);
2、禁止指令的重排序优化;
虽然多线程间可见,但是这个论据并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核心点在于java里的运算(比如自增)并不是原子性的。
发现一个类AtomicInteger类 这个类是原子性的 可以尝试用下这个
方案4: 使用arraylist保存创建的Dialog
public static void showLoading(Context context,String type){
final AppCompatDialog dialog = new AppCompatDialog(context, R.style.dialog);
final AVLoadingIndicatorView avLoadingIndicatorView = LoaderCreator.create(type,context);
dialog.setContentView(avLoadingIndicatorView);
int deviceWidth = DimenUtil.getScreenWidth(context);
int deviceHeight = DimenUtil.getScreenHeight(context);
final Window dialogWindow = dialog.getWindow();
if (dialogWindow!=null){
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
lp.width = deviceWidth/LOADER_SIZE_SCALE;
lp.height = deviceHeight/LOADER_SIZE_SCALE;
lp.height = lp.height + deviceHeight/LOADER_OFFSET_SCALE;
lp.gravity = Gravity.CENTER;
}
LOADERS.add(dialog);
dialog.show();
}
public static void showLoading(Context context){
showLoading(context,DEFAULT_LOADER);
}
public static void stopLoading(){
for(AppCompatDialog dialog:LOADERS){
if (dialog!=null){
if (dialog.isShowing()) {
dialog.cancel();
}
}
}
}
public static void showLoading(Context context,Enum<LoaderStyle> type){
showLoading(context,type.name());
}
存在线程并发问题
https://www.jianshu.com/p/fc2e551b907c
最终方案
使用计数器
AtomicInteger
下面的方案存在问题
串行请求的时候会有问题,如果第一个请求结束后再请求第二个就存在问题了.如果请求时间过长,会让对话框提前结束
思考
在计算机的各个领域中,有这样一种设计思想:当一个临时的对象可能会被多次复用时,给它添加一个定时器。某次任务执行完毕后,不要立即回收这个对象,而是开启定时器。在定时器结束前到来的请求,依旧交给它处理,并重置定时。如果超时,则销毁这个对象,下次使用时需要重新创建。如HTTP连接,数据库连接,线程池等等,都会用到这种思想。实际上我们的弹窗应用场景与这些也是十分类似,我们可以用相同的方法来解决这个问题。
在某次网络请求结束之后,不要立即取消弹窗,而是开启一个定时任务去取消。如果在定时结束之前再次发起请求,则继续使用这个弹窗,并重置定时。这样我们就算是优化了这个逻辑.
伪代码实现
- 把取消弹窗的逻辑放在Runnable 对象中,当调用dismissProgressDialog时,不是直接执行runnable,而是通过Handler.postDelay()方法 ,在200ms(这个时间可以自由控制 )后再取消弹窗。
- 而再进行弹窗的时候,先判断该runnable是否在消息队列中。如果还在,说明上次弹窗尚未被取消。因此只需要从消息队列中移除runnable即可保持弹窗还在。如果不存在,就重新弹窗
代码实现
package com.sinochem.www.car.owner.view;
import android.androidlib.utils.GlideUtils;
import android.androidlib.utils.Utils;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.sinochem.www.car.owner.R;
import com.sinochem.www.car.owner.utils.LogUtil;
import java.lang.ref.WeakReference;
import androidx.appcompat.app.AppCompatDialog;
public class LoadingFragment {
private static WeakReference<Context> contextWeakReference;
private String content;
private static AppCompatDialog loadingDialog;
private LoadingFragment() {
}
static Handler mHandler = new Handler();
private static Runnable runnable = new Runnable() {
@Override
public void run() {
LogUtil.d("runnable调用关闭对话框");
Context mContext = contextWeakReference.get();
if (mContext!=null && mContext.getClass().getName().contains("Activity") && !Utils.isDestroy((Activity) mContext)){
if (loadingDialog != null && loadingDialog.isShowing() == true) {
loadingDialog.dismiss();
}
}
loadingDialog = null;
}
};
public static void showLodingDialog(Context context) {
contextWeakReference = new WeakReference<>(context);
Context mContext = contextWeakReference.get();
showLodingDialogGif(mContext, false);
}
/**
* @param context
* @param mIsCancel true 返回键不取消 false 返回键取消
* @return
*/
private static Dialog showLodingDialogGif(Context context, boolean mIsCancel) {
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.loading_progress, null);// 得到加载view
ImageView gif = v.findViewById(R.id.iv_gif);
GlideUtils.loadFifLoop(context, R.mipmap.loading_gif, gif);
TextView tvContent = v.findViewById(R.id.message);
tvContent.setText("加载中...");
// 创建自定义样式dialog
if (loadingDialog == null) {
loadingDialog = new AppCompatDialog(context, R.style.ProgressHUD);
loadingDialog.setCancelable(true); // 是否可以按“返回键”消失
loadingDialog.setCanceledOnTouchOutside(false); // 点击加载框以外的区域
loadingDialog.setContentView(v, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));// 设置布局
if (mIsCancel)//点击返回键不取消
loadingDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
return true;
} else {
return false;
}
}
});
//将显示Dialog的方法封装在这里面
Window window = loadingDialog.getWindow();
if (window != null) {
window.clearFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND | WindowManager.LayoutParams.FLAG_DIM_BEHIND);
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setGravity(Gravity.CENTER);
window.setAttributes(lp);
}
// window.setWindowAnimations(R.style.PopWindowAnimStyle);
if (context.getClass().getName().contains("Activity") && !Utils.isDestroy((Activity) context)) {
loadingDialog.show();
// LOADERS.add(loadingDialog);
// LogUtil.d("对话框show 数量= " + LOADERS.size());
} else {
LogUtil.d("展示对话框前先消失");
dismiss();
}
} else {
// 上一个dialog还存在,先不取消,继续使用该dialog
mHandler.removeCallbacks(runnable);
}
return loadingDialog;
}
private static Dialog showLodingDialog(Context context, boolean mIsCancel) {
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.progress_hud_2, null);// 得到加载view
// carImg = v.findViewById(R.id.spinnerImageView);
TextView tvContent = v.findViewById(R.id.message);
// Animation rotateAnimation = AnimationUtils.loadAnimation(context, R.anim.rotate_anim);
// LinearInterpolator lin = new LinearInterpolator();
// rotateAnimation.setInterpolator(lin);
// carImg.startAnimation(rotateAnimation);
tvContent.setText("加载中...");
AppCompatDialog loadingDialog = new AppCompatDialog(context, R.style.ProgressHUD);// 创建自定义样式dialog
loadingDialog.setCancelable(true); // 是否可以按“返回键”消失
loadingDialog.setCanceledOnTouchOutside(false); // 点击加载框以外的区域
loadingDialog.setContentView(v, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));// 设置布局
if (mIsCancel)//点击返回键不取消
loadingDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
return true;
} else {
return false;
}
}
});
//将显示Dialog的方法封装在这里面
Window window = loadingDialog.getWindow();
if (window != null) {
window.clearFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND | WindowManager.LayoutParams.FLAG_DIM_BEHIND);
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setGravity(Gravity.CENTER);
window.setAttributes(lp);
}
// window.setWindowAnimations(R.style.PopWindowAnimStyle);
if (context.getClass().getName().contains("Activity") && !((Activity) context).isFinishing()) {
// LOADERS.add(loadingDialog);
loadingDialog.show();
// LogUtil.d("对话框show 数量= " + LOADERS.size());
} else {
LogUtil.d("展示对话框前先消失");
dismiss();
}
return loadingDialog;
}
/**
* 关闭dialog
*/
public static void dismiss() {
if (null != loadingDialog && loadingDialog.isShowing()) {
//// 延迟200ms再结束弹窗,简化连续调用接口的进度条管理
LogUtil.d("延迟200ms再结束弹窗");
mHandler.postDelayed(runnable, 200);
return;
}
// 1.移除Handler中可能存在的runnable 2.立即结束弹窗
// 通常用在Activity Destroy中,防止窗体泄漏
mHandler.removeCallbacks(runnable);
LogUtil.d("移除Handler中可能存在的runnable");
if (null != loadingDialog && loadingDialog.isShowing()) {
LogUtil.d("直接关闭对话框");
loadingDialog.dismiss();
loadingDialog = null;
}
}
}
关键代码
关闭对话框
if (null != loadingDialog && loadingDialog.isShowing()) {
//// 延迟200ms再结束弹窗,简化连续调用接口的进度条管理
LogUtil.d("延迟200ms再结束弹窗");
mHandler.postDelayed(runnable, 200);
return;
}
mHandler.removeCallbacks(runnable);
static Handler mHandler = new Handler();
private static Runnable runnable = new Runnable() {
@Override
public void run() {
LogUtil.d("runnable调用关闭对话框");
Context mContext = contextWeakReference.get();
if (mContext!=null && mContext.getClass().getName().contains("Activity") && !Utils.isDestroy((Activity) mContext)){
if (loadingDialog != null && loadingDialog.isShowing() == true) {
loadingDialog.dismiss();
}
}
loadingDialog = null;
}
};
创建时候判断是否有对话框,如果有对话框吧之前的消息队列删除
if (loadingDialog == null) {
}else{
mHandler.removeCallbacks(runnable);
}
串行请求的情况
如果有串行请求的情况 那么可以单独的吧对话框提到前面来 比如有两个请求, 那么在第一个请求之前弹出对话框,成功后不关闭对话框,直接进行下一个网络请求 等待第二个网络请求结束后 才关闭对话框
或者使用rxjava 解决串行问题
补充
上面代码还是有点问题, 主要出现在多线程并发情况, 对dialog的判断,是多线程并发可见性的问题, 需要把判断条件做一下改变,定义一个boolean 类型的变量isShow,对他使用volatile进行修饰,以及用这个值进行对话框是否展示的判定, 因为对基本类型赋值是原子性的. 用volatile 修饰让各线程都可见.这样就真正解决多线程并发的问题了
本文探讨了在多个网络请求并发时,如何优化对话框的显示逻辑,避免对话框频繁出现或过早消失,提出了使用Handler和Runnable的解决方案。
2285

被折叠的 条评论
为什么被折叠?



