iOS转android之Loading
前言
无论iOS还是Android进行网络请求时Loading都是不可或缺的控件,使用频率也是很高的,如果操作不当出现问题的可能性也就比较大,例如一个页面有多个网络请求那么这些网络请求对Loading的操作是容易出现冲突,见图片
Loading常见问题一、Android的Loading调研
根据iOS的使用习惯,Loading应该是一个单例类,使用KeyWindow控制其显示和隐藏,loading和Controller是没有任何依赖关系的,View或者Model都可以很容易控制Loading的显示和隐藏,那么这种用法就比较容易实现Loading和网络封装的结合,开始网络请求时显示Loading,结束时隐藏Loading,但是经过两天的调研我发现Android中很难实现这种效果,那个大佬可以实现请告诉我。为什么Android实现不了呢,因为Android中每一个Activity都相当于一个Window,没有全局的KeyWindow(以我目前理解,不对麻烦纠正谢谢🙏)
Window 有三种类型
- 应用 Window对应一个 Acitivity
- 子 Window 不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog 就是一个子 Window
- 系统 Window需要声明权限才能创建的 Window,比如 Toast 和系统状态栏都是系统 Window
如果想要实现显示在所有的Window上边的Loading就只能使用系统Window,但是我在了解过程中发现没有使用系统Window来显示Loading的,我申请权限没有成功,所以也没有尝试。
那么Android中实现Loading的方法就要依赖BaseActivity来实现,在基类中添加Loading,在子类中控制显示和消失
二、实现
在BaseActivity的布局文件中添加Loading控件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:background="#ffffff"
tools:context=".base.activity.BaseActivity"
android:id="@+id/main_root_layout"
>
<!--基类中其他布局-->
<!--Loading-->
<RelativeLayout
android:id="@+id/rl_progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00000000"
android:visibility="gone">
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
</RelativeLayout>
private Handler loadingHandler;
private RelativeLayout mRelativeLayoutLoading;
/**
* 显示和隐藏loadingView
* @param isShow true显示loading false隐藏loadingview
*/
public void showLoadingView(boolean isShow){
if (isShow){ //显示loading
loadingHandler.postDelayed(new Runnable() {
@Override
public void run() {
mRelativeLayoutLoading.bringToFront();
mRelativeLayoutLoading.setVisibility(View.VISIBLE);
}
},1);
}else {//隐藏laoding
loadingHandler.postDelayed(new Runnable() {
@Override
public void run() {
mRelativeLayoutLoading.bringToFront();
mRelativeLayoutLoading.setVisibility(View.GONE);
}
},1);
}
}
在BaseActivity的子类中使用也很简单
showLoadingView(true); //显示loading
showLoadingView(false); //隐藏loading
三、结合网络请求封装使用
在之前封装好的网络方法中添加两个参数
/**
*
* @param url 地址
* @param paraDic 参数
* @param method post/get
* @param baseActivity 请求网络请求的context
* @param isLoading 是否显示loading
* @param <T>
*/
public static <T extends BaseDao> void request( final String url, final HashMap<String,Object> paraDic, String method, final BaseActivity baseActivity, final boolean isLoading){
if (isLoading){
baseActivity.showLoadingView(true); //显示loading
}
call.enqueue(new Callback() {
@Override
public void onFailure( Call call, IOException e ) {
baseActivity.showLoadingView(false); //隐藏loading
}
@Override
public void onResponse( Call call, Response response ) throws IOException {
baseActivity.showLoadingView(false); //隐藏loading
}
四、问题解决方案
Loading常见问题一个Activity中有多个网络请求方法对Loading的显示是有影响的,那么怎么解决这个问题呢?在iOS开发中有一个很重要的概念——引用计数,每一个对象都有一个引用计数,被持有一次引用计数+1,被释放一次引用计数-1,直到处于可被回收的状态(引用计数为0并不一定马上被销毁),在iOS中一个app只有一个Loading的单例对象,声明一个数字num,Loading被启动一次num += 1
,loading被停止一次num -= 1
,如果num == 0
那么loading停止。
Android中没有全局的Loading对象,而是每一个activity对象中有一个Loading对象,那么我们将每一个activity对象都像iOS那样处理一下
/**
* loading队列对象类
*/
private static class LoadingCountDownManager{
private BaseActivity activity; //进行网络请求的activity对象
private int countDown = 0; //当前activity的loading引用计数,如果>0那么显示loading,否则不显示loading
public int getCountDown() { //获取当前activity对象的loading引用计数值
return countDown;
}
/**
* 赋值
* @param activity
*/
public void setActivity( BaseActivity activity ) {
this.activity = activity;
}
public BaseActivity getActivity() {
return activity;
}
/**
* 开始或者结束loading
* @param isAdd
* true当前activity对象开始了一次Loading
* true当前activity对象结束了一次Loading
*/
public void addOne( boolean isAdd){
if (isAdd){
countDown += 1;
}else {
countDown -= 1;
if (countDown < 0){
countDown = 0;
}
}
if (activity != null){
if (countDown > 0){ //当前activity显示loading
activity.showLoadingView(true);
}else { //当前activity不显示loading
activity.showLoadingView(false);
}
}
}
}
声明队列
//loading队列
private static ArrayList<LoadingCountDownManager> loadingArrayList = new ArrayList<LoadingCountDownManager>();
/**
* 为了解决同一个页面多个网络请求对loading显示效果的影响,使用引用计数的方法来管理每个Activity中的loading
* @param activity
* @param isBeginLoading
*/
private static void loadingManager( BaseActivity activity, boolean isBeginLoading ){
if (activity != null){
int exitIndex = -1; //如果当前activity已经存在于loading队列中,那么获取其位置
for (int i=0; i<loadingArrayList.size(); i++){
if (loadingArrayList.get(i).getActivity() != null){
if (loadingArrayList.get(i).getActivity().getClass().getName() == activity.getClass().getName()){
loadingArrayList.get(i).addOne(isBeginLoading);
exitIndex = I;
}
}
}
if (exitIndex >= 0){
if (loadingArrayList.get(exitIndex).getCountDown() == 0){ //对应activity的loading要消失
loadingArrayList.remove(exitIndex); //将没有loading的activity移除队列
}
}else {//当前activity是第一次开始loading,需要插入队列
LoadingCountDownManager loadingCountDownManager = new LoadingCountDownManager();
loadingCountDownManager.setActivity(activity);
loadingCountDownManager.addOne(true);
loadingArrayList.add(loadingCountDownManager);
}
}
}
使用方法
/**
*
* @param url 地址
* @param paraDic 参数
* @param method post/get
* @param baseActivity 请求网络请求的context
* @param isLoading 是否显示loading
* @param <T>
*/
public static <T extends BaseDao> void request( final String url, final HashMap<String,Object> paraDic, String method, final BaseActivity baseActivity, final boolean isLoading){
if (isLoading){
loadingManager(baseActivity,true); //显示loading
}
call.enqueue(new Callback() {
@Override
public void onFailure( Call call, IOException e ) {
loadingManager(baseActivity,false); //隐藏loading
}
@Override
public void onResponse( Call call, Response response ) throws IOException {
loadingManager(baseActivity,false); //隐藏loading
}
参考文章
https://blog.csdn.net/yhaolpz/article/details/68936932
https://www.jianshu.com/p/66026e9aa734
https://blog.csdn.net/dr_abandon/article/details/52493917
https://blog.csdn.net/android_cmos/article/details/73382573