文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android使用Fragment打造万能页面切换框架

2022-06-06 09:19

关注

首先我们来回忆一下传统用Activity进行的页面切换,activity之间切换,首先需要新建intent对象,给该对象设置一些必须的参数,然后调用startActivity方法进行页面跳转。如果需要activity返回结果,则调用startActivityForResult方法,在onActivityResult方法中获得返回结果。此外,每一个要展示的activity需要在AndroidManifest.xml文件中注册。而且,如果在某些特定的情况下(比如65536方法数爆炸)要动态加载dex,还得手动管理activity的生命周期。那么,有没有这么一种方法进行页面切换时,无需在AndroidManifest.xml文件中声明这些信息,动态加载时又无需我们管理生命周期,等等优点呢。

我们来回忆一下,在android3.0之后,谷歌出了一个Fragment,这个东西依赖于activity,其生命周期由宿主activity进行管理,并且可以通过FragmentManager和FragmentTransaction等相关的类进行管理。那么我们能不能从Fragment入手,打造一个完全由Fragment组成的页面跳转框架呢。

使用Fragment其实很简单,首先开启一个事务,通过add,replace,remove等方法进行添加,替换,移除等操作,这一切的操作可能需要依赖一个容器,这个容器提供一个id,进行对应操作时将这个id作为参数传入。之后通过相应方法提交事务就可以了,就像这样子。

FragmentManager fragmentManager = getSupportFragmentManager();  FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();  fragmentTransaction.replace(R.id.fragment_container, fragment);  fragmentTransaction.commit(); 

然而我相信你一定有这样的经历,在使用Fragment进行页面切换时又得不断用代码控制其显示与隐藏的逻辑,那么有没有这样一种方法在程序中不断复用这段代码呢?

首先,我们希望Fragment能像Activity那样,进行正确的跳转。那么需要什么,答案是Fragment对象,我们肯定需要它的Class全类名,当然跳转的时候可能会带上一些参数,这个参数应该通过Bundle进行传递。而且,全类名可能太长,不便记忆,我们参考web的架构,应该还要取一个别名alias。就这样,一个Fragment页面的三个基本属性就被我们抽取出来了,组成了如下的实体类。在这个实体类中,页面传递的参数为json形式的String字符串对象,在需要使用的时候我们通过该json构造出bundle。页面名变量mName是整个程序唯一标示该页面的参数,其值唯一,但是其对应的class全类名可以不唯一,也就是说从name到class的映射可以一对多。


public class CorePage implements Serializable {
  private static final long serialVersionUID = 3736359137726536495L;
  private String mName;
  //页面名
  private String mClazz;
  //页面class
  private String mParams;
  //传入参数,json object结构
  public CorePage(String name, String clazz, String params) {
    mName = name;
    mClazz = clazz;
    mParams = params;
  }
  public String getClazz() {
    return mClazz;
  }
  public void setClazz(String clazz) {
    mClazz = clazz;
  }
  public String getName() {
    return mName;
  }
  public void setName(String name) {
    mName = name;
  }
  public String getParams() {
    return mParams;
  }
  public void setParams(String params) {
    mParams = params;
  }
  @Override
  public String toString() {
    return "Page{" +
        "mName='" + mName + '\'' +
        ", mClazz='" + mClazz + '\'' +
        ", mParams='" + mParams + '\'' +
        '}';
  }
}

实体类编写好了,为了更方便的进行页面跳转,我们需要像Activity那样,有一个配置文件,里面存着Fragment名到其全类名的映射关系。那么这些数据存在哪呢。我们参考网络数据,一般从网络上获取的数据有两种格式,一种是json,一种是xml,json由于其优点,在网络传输中被大量使用,这里,我们优先使用json,选定了json之后,就要选定一个json解析的框架,我们不使用android系统自带的,我们使用阿里的fastjson,当然你也可以使用gson或者jackson。我们的Fragment有很多,所以这个Fragment的配置文件应该是一个json数组。就像这个样子


[
 {
  "name": "test1",
  "class": "cn.edu.zafu.corepage.sample.TestFragment1",
  "params": {
   "param1": "value1",
   "param2": "value2"
  }
 },
 {
  "name": "test2",
  "class": "cn.edu.zafu.corepage.sample.TestFragment2",
  "params": ""
 }
]

有了这个配置,我们就要在程序进入时读取这个配置。我们将这个配置放在assets目录下,当然你也可以放在其他目录下,只有你能读取到就行,甚至你可以放在压缩包里。然而,实际情况下,这个文件不应该暴露,因为一旦暴露就存在风险。因此,大家可以采用更安全的方式存这些数据,比如把数据压缩到压缩包中,增加一个密码,而读取文件的内容的代码我们移到native中取实现,毕竟java层太容易被反编译了,而c/c++层相对来说会毕竟有难度。

这里为了简单,我们暂时放在assets目录下,那么要从assets目录中读取这个文件内容就必须有这么一个读取该目录下文件的函数,该目录在android中也算是一个比较特殊的目录了,可以通过getAssets()函数,然后获得一个输入流,将文件内容读出,然后将对应的json解析出来就可以了。


  
  private String readFileFromAssets(Context context, String fileName) {
    String result = "";
    try {
      InputStreamReader inputReader = new InputStreamReader(context.getResources().getAssets().open(fileName));
      BufferedReader bufReader = new BufferedReader(inputReader);
      String line = "";
      while ((line = bufReader.readLine()) != null)
        result += line;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return result;
  }

然后根据该文件内容读取json配置。读取出来后需要将这些数据保存下来。因此要有一个数据结构来保存这个对象,存完之后还要方便取出,存取的依据应该是Fragment的表示,即前面提到的name,因此Map这个数据结构是最适合不过了。


private Map<String, CorePage> mPageMap = new HashMap<String, CorePage>();
//保存page的map

将配置读取出来存进该map,读取的时候判断name和class是否为空,为空则跳过。



private void readConfig() {
  Log.d(TAG, "readConfig from json");
  String content = readFileFromAssets(mContext, "page.json");
  JSONArray jsonArray = JSON.parseArray(content);
  Iterator<Object> iterator = jsonArray.iterator();
  JSONObject jsonPage = null;
  String pageName = null;
  String pageClazz = null;
  String pageParams = null;
  while (iterator.hasNext()) {
    jsonPage = (JSONObject) iterator.next();
    pageName = jsonPage.getString("name");
    pageClazz = jsonPage.getString("class");
    pageParams = jsonPage.getString("params");
    if (TextUtils.isEmpty(pageName) || TextUtils.isEmpty(pageClazz)) {
      Log.d(TAG, "page Name is null or pageClass is null");
      return;
    }
    mPageMap.put(pageName, new CorePage(pageName, pageClazz, pageParams));
    Log.d(TAG, "put a page:" + pageName);
  }
  Log.d(TAG, "finished read pages,page size:" + mPageMap.size());
}

此外,除了从配置文件中读取,我们应该可以动态添加,对外提供这个函数。


  
  public boolean putPage(String name, Class<? extends BaseFragment> clazz, Map<String, String> params) {
    if (TextUtils.isEmpty(name) || clazz == null) {
      Log.d(TAG, "page Name is null or pageClass is null");
      return false;
    }
    if (mPageMap.containsKey(name)) {
      Log.d(TAG, "page has already put!");
      return false;
    }
    CorePage corePage = new CorePage(name, clazz.getName(), buildParams(params));
    Log.d(TAG, "put a page:" + name);
    return true;
  }
  
  private String buildParams(Map<String, String> params) {
    if (params == null) {
      return "";
    }
    String result = JSON.toJSONString(params);
    Log.d(TAG, "params:" + result);
    return result;
  }

文章开头已经说了,页面跳转的参数是json形式的字符串,我们还要这么一个函数,能够根据json字符串构造出一个bundle



  private Bundle buildBundle(CorePage corePage) {
    Bundle bundle = new Bundle();
    String key = null;
    Object value = null;
    if (corePage != null && corePage.getParams() != null) {
      JSONObject j = JSON.parseObject(corePage.getParams());
      if (j != null) {
        Set<String> keySet = j.keySet();
        if (keySet != null) {
          Iterator<String> ite = keySet.iterator();
          while (ite.hasNext()) {
            key = ite.next();
            value = j.get(key);
            bundle.putString(key, value.toString());
          }
        }
      }
    }
    return bundle;
  }

以上配置读取的一系列函数,构成了页面管理类CorePageManager,我们对其应用单例模式。




public class CorePageManager {
  private volatile static CorePageManager mInstance = null;
  //单例
  private Context mContext;
  //Context上下文
  
  private CorePageManager() {
  }
  
  public static CorePageManager getInstance() {
    if (mInstance == null) {
      synchronized (CorePageManager.class) {
        if (mInstance == null) {
          mInstance = new CorePageManager();
        }
      }
    }
    return mInstance;
  }
  
  public void init(Context context) {
    try {
      mContext = context.getApplicationContext();
      readConfig();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

其中init函数暴露给程序入口,进行配置文件的读取。一般放在Application的子类的onCreate方法中即可。

到这里为止,基本上我们一切已经就绪了。就差如何切换了。这里,在CorePageManager类中再提供两个核心函数,用于处理页面切换。

下面这个函数是页面切换的核心函数,首先根据参数从map中拿到对应的实体类,假设存在这个页面,通过class名用反射获得该Fragment对象,调用前面写好的创建Bundle的函数得到页面参数,并与当前函数中的入参bundle进行合并。将参数设置给fragment对象,开启一个fragment事务,查找id为fragment_container的fragment容器,如果该容器已经有fragment,则隐藏它,如果该函数传递了动画参数,则添加页面切换动画,然后将反射获得的fragment对象添加到该容器中,如果需要添加到返回栈,则调用addToBackStack,最后提交事务并返回该fragment对象。

整个函数很简单,就是我们平常在activity中写的切换fragment的代码


 
  public Fragment openPageWithNewFragmentManager(FragmentManager fragmentManager, String pageName, Bundle bundle, int[] animations, boolean addToBackStack) {
    BaseFragment fragment = null;
    try {
      CorePage corePage = this.mPageMap.get(pageName);
      if (corePage == null) {
        Log.d(TAG, "Page:" + pageName + " is null");
        return null;
      }
      fragment = (BaseFragment) Class.forName(corePage.getClazz()).newInstance();
      Bundle pageBundle = buildBundle(corePage);
      if (bundle != null) {
        pageBundle.putAll(bundle);
      }
      fragment.setArguments(pageBundle);
      fragment.setPageName(pageName);
      FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
      if (animations != null && animations.length >= 4) {
        fragmentTransaction.setCustomAnimations(animations[0], animations[1], animations[2], animations[3]);
      }
      Fragment fragmentContainer = fragmentManager.findFragmentById(R.id.fragment_container);
      if (fragmentContainer != null) {
        fragmentTransaction.hide(fragmentContainer);
      }
      fragmentTransaction.add(R.id.fragment_container, fragment, pageName);
      if (addToBackStack) {
        fragmentTransaction.addToBackStack(pageName);
      }
      fragmentTransaction.commitAllowingStateLoss();
      //fragmentTransaction.commit();
    } catch (Exception e) {
      e.printStackTrace();
      Log.d(TAG, "Fragment.error:" + e.getMessage());
      return null;
    }
    return fragment;
  }

而上面这个函数中的id值在一个基础的布局中,之后的Fragment都会添加到该布局中去。我们的基类Activity也将使用这个布局,这个后续编写BaseActivity的时候会提到


<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/fragment_container"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" >
</FrameLayout>

此外,我们再提供一个核心函数。就是如果返回栈中存在了目标fragment,则将其弹出,否则新建fragment打开。


 
  public Fragment gotoPage(FragmentManager fragmentManager, String pageName, Bundle bundle, int[] animations) {
    Log.d(TAG, "gotoPage:" + pageName);
    Fragment fragment = null;
    if (fragmentManager != null) {
      fragment = fragmentManager.findFragmentByTag(pageName);
    }
    if (fragment != null) {
      fragmentManager.popBackStackImmediate(pageName, 0);
    } else {
      fragment = this.openPageWithNewFragmentManager(fragmentManager, pageName, bundle, animations, true);
    }
    return fragment;
  }

细心的你可能已经注意到了页面跳转函数中用到了动画,其实这个动画是一个数组,为了方便使用,我们将其封装为枚举类,提供常见的几种动画形式。


package cn.edu.zafu.corepage.core;

public enum CoreAnim {
  none, 
  present, 
  slide,
  fade;
}

之后我们还要根据该枚举类获得对应的动画的xml文件。



  public static int[] convertAnimations(CoreAnim coreAnim) {
    if (coreAnim == CoreAnim.present) {
      int[] animations = {R.anim.push_in_down, R.anim.push_no_ani, R.anim.push_no_ani, R.anim.push_out_down};
      return animations;
    } else if (coreAnim == CoreAnim.fade) {
      int[] animations = {R.anim.alpha_in, R.anim.alpha_out, R.anim.alpha_in, R.anim.alpha_out};
      return animations;
    } else if (coreAnim == CoreAnim.slide) {
      int[] animations = {R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right};
      return animations;
    }
    return null;
  }

这里贴出一个alpha_in.xml中的代码,其他文件类似,这些文件都位于res/anim目录下


<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="@android:integer/config_mediumAnimTime"
  android:fromAlpha="0.0"
  android:toAlpha="1.0" />

到了这里,如果你都明白了,那么后面基本上就没有什么难度了,因为之后的功能都是基于以上内容。

前面我们定义了一个CorePage实体类用于保存配置文件中实体类的信息,而页面切换过程中需要传递一些参数,比如是否添加到fragment返回栈,是否在新的activity中打开fragment,页面切换时的动画,传递的参数等等,通样,我们将其封装为实体类。由于该对象可能需要通过intent传递,这里我们将其实现Parcelable接口。实现该接口方法很简单,假设使用的是android studio,使用快捷键alt+insert选择Parcelable即可创建一个模板,我们将其补齐就好了。整个类如下,我们对外提供了多个重载的构造函数,其本质都是一样的,而前面的动画转换函数我们将其放入这个类中。



public class CoreSwitchBean implements Parcelable {
  public static final Parcelable.Creator<CoreSwitchBean> CREATOR = new Parcelable.Creator<CoreSwitchBean>() {
    @Override
    public CoreSwitchBean createFromParcel(Parcel in) {
      return new CoreSwitchBean(in);
    }
    @Override
    public CoreSwitchBean[] newArray(int size) {
      return new CoreSwitchBean[size];
    }
  };
  private String mPageName;
  //页面名
  private Bundle mBundle;
  //相关数据
  private int[] mAnim = null;
  //动画类型
  private boolean mAddToBackStack = true;
  //是否添加到栈中
  private boolean mNewActivity = false;
  //是否起新的Activity
  private int requestCode = -1;
  //fragment跳转
  public CoreSwitchBean(String pageName) {
    this.mPageName = pageName;
  }
  public CoreSwitchBean(String pageName, Bundle bundle) {
    this.mPageName = pageName;
    this.mBundle = bundle;
  }
  public CoreSwitchBean(String pageName, Bundle bundle, CoreAnim coreAnim) {
    this.mPageName = pageName;
    this.mBundle = bundle;
    this.setAnim(coreAnim);
  }
  public void setAnim(CoreAnim anim) {
    mAnim = convertAnimations(anim);
  }
  
  public static int[] convertAnimations(CoreAnim coreAnim) {
    if (coreAnim == CoreAnim.present) {
      int[] animations = {R.anim.push_in_down, R.anim.push_no_ani, R.anim.push_no_ani, R.anim.push_out_down};
      return animations;
    } else if (coreAnim == CoreAnim.fade) {
      int[] animations = {R.anim.alpha_in, R.anim.alpha_out, R.anim.alpha_in, R.anim.alpha_out};
      return animations;
    } else if (coreAnim == CoreAnim.slide) {
      int[] animations = {R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right};
      return animations;
    }
    return null;
  }
  public CoreSwitchBean(String pageName, Bundle bundle, int[] anim) {
    this.mPageName = pageName;
    this.mBundle = bundle;
    this.mAnim = anim;
  }
  public CoreSwitchBean(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack) {
    this.mPageName = pageName;
    this.mBundle = bundle;
    this.setAnim(coreAnim);
    this.mAddToBackStack = addToBackStack;
  }
  public CoreSwitchBean(String pageName, Bundle bundle, int[] anim, boolean addToBackStack) {
    this.mPageName = pageName;
    this.mBundle = bundle;
    this.mAnim = anim;
    this.mAddToBackStack = addToBackStack;
  }
  public CoreSwitchBean(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack, boolean newActivity) {
    this.mPageName = pageName;
    this.mBundle = bundle;
    this.setAnim(coreAnim);
    this.mAddToBackStack = addToBackStack;
    this.mNewActivity = newActivity;
  }
  public CoreSwitchBean(String pageName, Bundle bundle, int[] anim, boolean addToBackStack, boolean newActivity) {
    this.mPageName = pageName;
    this.mBundle = bundle;
    this.mAnim = anim;
    this.mAddToBackStack = addToBackStack;
    this.mNewActivity = newActivity;
  }
  public CoreSwitchBean(String pageName, Bundle bundle, int[] anim, boolean addToBackStack, boolean newActivity, int requestCode) {
    this.mPageName = pageName;
    this.mBundle = bundle;
    this.mAnim = anim;
    this.mAddToBackStack = addToBackStack;
    this.mNewActivity = newActivity;
    this.requestCode = requestCode;
  }
  protected CoreSwitchBean(Parcel in) {
    mPageName = in.readString();
    mBundle = in.readBundle();
    int[] a = {in.readInt(), in.readInt(), in.readInt(), in.readInt()};
    mAnim = a;
    mAddToBackStack = in.readInt() == 1 ? true : false;
    mNewActivity = in.readInt() == 1 ? true : false;
    requestCode = in.readInt();
  }
  public String getPageName() {
    return mPageName;
  }
  public void setPageName(String pageName) {
    mPageName = pageName;
  }
  public boolean isNewActivity() {
    return mNewActivity;
  }
  public void setNewActivity(boolean newActivity) {
    mNewActivity = newActivity;
  }
  public boolean isAddToBackStack() {
    return mAddToBackStack;
  }
  public void setAddToBackStack(boolean addToBackStack) {
    mAddToBackStack = addToBackStack;
  }
  public int[] getAnim() {
    return mAnim;
  }
  public void setAnim(int[] anim) {
    mAnim = anim;
  }
  public Bundle getBundle() {
    return mBundle;
  }
  public void setBundle(Bundle bundle) {
    mBundle = bundle;
  }
  public int getRequestCode() {
    return requestCode;
  }
  public void setRequestCode(int requestCode) {
    this.requestCode = requestCode;
  }
  @Override
  public String toString() {
    return "SwitchBean{" +
        "mPageName='" + mPageName + '\'' +
        ", mBundle=" + mBundle +
        ", mAnim=" + Arrays.toString(mAnim) +
        ", mAddToBackStack=" + mAddToBackStack +
        ", mNewActivity=" + mNewActivity +
        ", requestCode=" + requestCode +
        '}';
  }
  @Override
  public int describeContents() {
    return 0;
  }
  @Override
  public void writeToParcel(Parcel out, int flags) {
    if (mPageName == null) {
      mPageName = "";
    }
    if (mBundle == null) {
      mBundle = new Bundle();
    }
    if (mAnim == null) {
      int[] a = {-1, -1, -1, -1};
      mAnim = a;
    }
    out.writeString(mPageName);
    mBundle.writeToParcel(out, flags);
    if (mAnim != null && mAnim.length == 4) {
      out.writeInt(mAnim[0]);
      out.writeInt(mAnim[1]);
      out.writeInt(mAnim[2]);
      out.writeInt(mAnim[3]);
    } else {
      out.writeInt(-1);
      out.writeInt(-1);
      out.writeInt(-1);
      out.writeInt(-1);
    }
    out.writeInt(mAddToBackStack ? 1 : 0);
    out.writeInt(mNewActivity ? 1 : 0);
    out.writeInt(requestCode);
  }
}

该类中的部分属性有一些默认值,比如是否添加到返回栈,是否起新Activity,我们默认在当前activity中打开fragment,并且添加到返回栈。有了这个类,之后的页面切换都通过该实体类进行传参就可以了。

然后,我们定义一个接口,让基类activity实现该接口,用于切换时的一些常用操作。fragment中调用宿主activity中该接口的方法即可。



public interface CoreSwitcher {
  
  void popPage();
  
  boolean isFragmentTop(String fragmentTag);
  
  boolean findPage(final String pageName);
  
  Fragment gotoPage(CoreSwitchBean bean);
  
  Fragment openPage(CoreSwitchBean bean);
  
  void removeUnlessFragment(List<String> fragmentLists);
  
  public Fragment openPageForResult(final CoreSwitchBean page, final BaseFragment fragment);
}

到了这里,似乎已经初具模型了,接下来,我们实现该接口。为了保证在子线程中也能调用这些方法,我们需要一个主线程的handler来帮我们完成一部分工作。假设我们已经获得了这个handler。具体细节看下面的代码实现吧,仔细阅读以下不难理解的。


  private static List<WeakReference<BaseActivity>> mActivities = new ArrayList<WeakReference<BaseActivity>>();
  //所有activity的引用
  private Handler mHandler = null;
  //线程安全的handler
  private WeakReference<BaseActivity> mCurrentInstance = null;
  //当前activity的引用
  
  @Override
  public void popPage() {
    popOrFinishActivity();
    //如果只有一个Fagment则退出activty
  }
  
  private void popOrFinishActivity() {
    if (this.isFinishing()) {
      return;
    }
    if (this.getSupportFragmentManager().getBackStackEntryCount() > 1) {
      if (isMainThread()) {
        this.getSupportFragmentManager().popBackStackImmediate();
      } else {
        this.mHandler.post(new Runnable() {
          @Override
          public void run() {
            getSupportFragmentManager().popBackStackImmediate();
          }
        });
      }
    } else {
      finishActivity(this, true);
    }
  }
  
  private boolean isMainThread() {
    return Thread.currentThread() == this.getMainLooper().getThread();
  }
  
  @Override
  public boolean isFragmentTop(String fragmentTag) {
    int size = mActivities.size();
    if (size > 0) {
      WeakReference<BaseActivity> ref = mActivities.get(size - 1);
      BaseActivity item = ref.get();
      if (item != null && item == this) {
        FragmentActivity activity = item;
        FragmentManager manager = activity.getSupportFragmentManager();
        if (manager != null) {
          int count = manager.getBackStackEntryCount();
          if (count >= 1) {
            FragmentManager.BackStackEntry entry = manager.getBackStackEntryAt(count - 1);
            if (entry.getName().equalsIgnoreCase(fragmentTag)) {
              return true;
            }
          }
        }
      }
    }
    return false;
  }
  
  @Override
  public boolean findPage(String pageName) {
    int size = mActivities.size();
    int j = size - 1;
    boolean hasFind = false;
    for (; j >= 0; j--) {
      WeakReference<BaseActivity> ref = mActivities.get(j);
      if (ref != null) {
        BaseActivity item = ref.get();
        if (item == null) {
          Log.d(TAG, "item is null");
          continue;
        }
        FragmentManager manager = item.getSupportFragmentManager();
        int count = manager.getBackStackEntryCount();
        for (int i = count - 1; i >= 0; i--) {
          String name = manager.getBackStackEntryAt(i).getName();
          if (name.equalsIgnoreCase(pageName)) {
            hasFind = true;
            break;
          }
        }
        if (hasFind) {
          break;
        }
      }
    }
    return hasFind;
  }
  
  @Override
  public Fragment gotoPage(CoreSwitchBean page) {
    if (page == null) {
      Log.e(TAG, "page name empty");
      return null;
    }
    String pageName = page.getPageName();
    if (!findPage(pageName)) {
      Log.d(TAG, "Be sure you have the right pageName" + pageName);
      return this.openPage(page);
    }
    int size = mActivities.size();
    int i = size - 1;
    for (; i >= 0; i--) {
      WeakReference<BaseActivity> ref = mActivities.get(i);
      if (ref != null) {
        BaseActivity item = ref.get();
        if (item == null) {
          Log.d(TAG, "item null");
          continue;
        }
        boolean findInActivity = popFragmentInActivity(pageName, page.getBundle(), item);
        if (findInActivity) {
          break;
        } else {
          item.finish();
          // 找不到就弹出
        }
      }
    }
    return null;
  }
  
  protected boolean popFragmentInActivity(final String pageName, Bundle bundle, BaseActivity findAcitivity) {
    if (pageName == null || findAcitivity == null || findAcitivity.isFinishing()) {
      return false;
    } else {
      final FragmentManager fragmentManager = findAcitivity.getSupportFragmentManager();
      if (fragmentManager != null) {
        Fragment frg = fragmentManager.findFragmentByTag(pageName);
        if (frg != null && frg instanceof BaseFragment) {
          if (fragmentManager.getBackStackEntryCount() > 1 && mHandler != null) {
            mHandler.postDelayed(new Runnable() {
              @Override
              public void run() {
                fragmentManager.popBackStack(pageName, 0);
              }
            }, 100);
          }
          ((BaseFragment) frg).onFragmentDataReset(bundle);//默认为空实现,用于舒心返回时的页面数据,重写改方法完成数据刷新
          return true;
        }
      }
    }
    return false;
  }
  
  public void startActivity(CoreSwitchBean page) {
    try {
      Intent intent = new Intent(this, BaseActivity.class);
      intent.putExtra("SwitchBean", page);
      this.startActivity(intent);
      int[] animations = page.getAnim();
      if (animations != null && animations.length >= 2) {
        this.overridePendingTransition(animations[0], animations[1]);
      }
    } catch (Exception e) {
      e.printStackTrace();
      Log.e(TAG, e.getMessage());
    }
  }

  @Override
  public Fragment openPage(CoreSwitchBean page) {
    boolean addToBackStack = page.isAddToBackStack();
    boolean newActivity = page.isNewActivity();
    Bundle bundle = page.getBundle();
    int[] animations = page.getAnim();
    if (newActivity) {
      startActivity(page);
      return null;
    } else {
      String pageName = page.getPageName();
      return CorePageManager.getInstance().openPageWithNewFragmentManager(getSupportFragmentManager(), pageName, bundle, animations, addToBackStack);
    }
  }
  
  @Override
  public void removeUnlessFragment(List<String> fragmentLists) {
    if (this.isFinishing()) {
      return;
    }
    FragmentManager manager = getSupportFragmentManager();
    if (manager != null) {
      FragmentTransaction transaction = manager.beginTransaction();
      for (String tag : fragmentLists) {
        Fragment fragment = manager.findFragmentByTag(tag);
        if (fragment != null) {
          transaction.remove(fragment);
        }
      }
      transaction.commitAllowingStateLoss();
      int count = manager.getBackStackEntryCount();
      if (count == 0) {
        this.finish();
      }
    }
  }
  
  @Override
  public Fragment openPageForResult(CoreSwitchBean page, BaseFragment fragment) {
    if (page != null) {
      if (page.isNewActivity()) {
        Log.d(TAG,"openPageForResult start new activity-----"+fragment.getPageName());
        mFragmentForResult=fragment;
        mFragmentRequestCode=page.getRequestCode();
        startActivityForResult(page);
        return null;
      }else{
        String pageName=page.getPageName();
        Bundle bundle=page.getBundle();
        int[] animations=page.getAnim();
        boolean addToBackStack=page.isAddToBackStack();
        BaseFragment frg = (BaseFragment) CorePageManager.getInstance().openPageWithNewFragmentManager(getSupportFragmentManager(), pageName, bundle, animations, addToBackStack);
        if (frg==null){
          return null;
        }
        final BaseFragment opener= fragment;
        frg.setRequestCode(page.getRequestCode());
        frg.setFragmentFinishListener(new BaseFragment.OnFragmentFinishListener() {
          @Override
          public void onFragmentResult(int requestCode, int resultCode, Intent intent) {
            opener.onFragmentResult(requestCode,resultCode,intent);
          }
        });
        return frg;
      }
    }else{
      Log.d(TAG, "openPageForResult.SwitchBean is null");
    }
    return null;
  }
  public void startActivityForResult(CoreSwitchBean page) {
    try {
      Intent intent = new Intent(this, BaseActivity.class);
      intent.putExtra("SwitchBean", page);
      intent.putExtra("startActivityForResult", "true");
      this.startActivityForResult(intent, page.getRequestCode());
      int[] animations = page.getAnim();
      if (animations != null && animations.length >= 2) {
        this.overridePendingTransition(animations[0], animations[1]);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "onActivityResult from baseActivity" + requestCode + " " + resultCode);
    if (mFragmentRequestCode == requestCode && mFragmentForResult != null) {
      mFragmentForResult.onFragmentResult(mFragmentRequestCode, resultCode, data);
    }
    super.onActivityResult(requestCode, resultCode, data);
  }

除此之外,提供一些函数的重载便于调用以及一些工具函数。



  
  private BroadcastReceiver mExitReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      String action = intent.getAction();
      if (action.equals(Config.ACTION_EXIT_APP)) {
        Log.d(TAG,"exit from broadcast");
        finish();
      }
    }
  };
  
  public static BaseActivity getTopActivity() {
    if (mActivities != null) {
      int size = mActivities.size();
      if (size >= 1) {
        WeakReference<BaseActivity> ref = mActivities.get(size - 1);
        if (ref != null) {
          return ref.get();
        }
      }
    }
    return null;
  }
  
  public static void unInit() {
    if (mActivities != null) {
      mActivities.clear();
    }
  }
  
  protected String getPageName() {
    BaseFragment frg = getActiveFragment();
    if (frg != null) {
      return frg.getPageName();
    }
    return "";
  }
  
  public Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack, boolean newActivity) {
    CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, coreAnim, addToBackStack, newActivity);
    return openPage(page);
  }
  
  public Fragment openPage(String pageName, Bundle bundle, int[] anim, boolean addToBackStack, boolean newActivity) {
    CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, anim, addToBackStack, newActivity);
    return openPage(page);
  }
  
  public Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack) {
    CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, coreAnim, addToBackStack);
    return openPage(page);
  }
  
  public Fragment openPage(String pageName, Bundle bundle, int[] anim, boolean addToBackStack) {
    CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, anim, addToBackStack);
    return openPage(page);
  }
  
  public Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim) {
    CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, coreAnim);
    return openPage(page);
  }
  
  public Fragment openPage(String pageName, Bundle bundle, int[] anim) {
    CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, anim);
    return openPage(page);
  }
  
  @Override
  public void onBackPressed() {
    if (this.getSupportFragmentManager().getBackStackEntryCount() == 1) {
      this.finishActivity(this, true);
    } else {
      super.onBackPressed();
    }
  }
  
  @Override
  public boolean onKeyDown(int keyCode, KeyEvent event) {
    BaseFragment activeFragment = getActiveFragment();
    boolean isHanlde = false;
    if (activeFragment != null) {
      isHanlde = activeFragment.onKeyDown(keyCode, event);
    }
    if (!isHanlde) {
      return super.onKeyDown(keyCode, event);
    } else {
      return isHanlde;
    }
  }
  
  public BaseFragment getActiveFragment() {
    if (this.isFinishing()) {
      return null;
    }
    FragmentManager manager = this.getSupportFragmentManager();
    if (manager != null) {
      int count = manager.getBackStackEntryCount();
      if (count > 0) {
        String tag = manager.getBackStackEntryAt(count - 1).getName();
        return (BaseFragment) manager.findFragmentByTag(tag);
      }
    }
    return null;
  }
  
  private void printAllActivities() {
    Log.d(TAG, "------------BaseActivity print all------------activities size:" + mActivities.size());
    for (WeakReference<BaseActivity> ref : mActivities) {
      if (ref != null) {
        BaseActivity item = ref.get();
        if (item != null) {
          Log.d(TAG, item.toString());
        }
      }
    }
  }
  
  private void finishActivity(BaseActivity activity, boolean showAnimation) {
    if (activity != null) {
      activity.finish();
    }
    if (showAnimation) {
      //动画
      int[] animations = null;
      if (activity.mFirstCoreSwitchBean != null && activity.mFirstCoreSwitchBean.getAnim() != null) {
        animations = activity.mFirstCoreSwitchBean.getAnim();
      }
      if (animations != null && animations.length >= 4) {
        overridePendingTransition(animations[2], animations[3]);
      }
    }
  }

并在BaseActivity的onCreate方法中完成一些初始化工作。



public class BaseActivity extends FragmentActivity implements CoreSwitcher {
  private static final String TAG = BaseActivity.class.getSimpleName();
  private static List<WeakReference<BaseActivity>> mActivities = new ArrayList<WeakReference<BaseActivity>>();
  protected CoreSwitchBean mFirstCoreSwitchBean;//记录首个,用于页面切换
  //所有activity的引用
  private Handler mHandler = null;
  private WeakReference<BaseActivity> mCurrentInstance = null;
  //当前activity的引用
  private BaseFragment mFragmentForResult = null;
  //forResult 的fragment
  private int mFragmentRequestCode = -1;
  //请求码,必须大于等于0
  
  private BroadcastReceiver mExitReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      String action = intent.getAction();
      if (action.equals(Config.ACTION_EXIT_APP)) {
        Log.d(TAG,"exit from broadcast");
        finish();
      }
    }
  };
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_base);
    Intent mNewIntent = getIntent();
    //处理新开activity的情况
    if (null != savedInstanceState) {
      loadActivitySavedData(savedInstanceState);
      //恢复数据
      //需要用注解SaveWithActivity
    }
    mHandler = new Handler(getMainLooper());
    //获得主线程handler
    mCurrentInstance = new WeakReference<BaseActivity>(this);
    //当前activity弱引用
    mActivities.add(mCurrentInstance);
    //当前activity增加到activity列表中
    printAllActivities();
    //打印所有activity情况
    init(mNewIntent);
    //处理新开activity跳转
    IntentFilter filter = new IntentFilter();
    filter.addAction(Config.ACTION_EXIT_APP);
    filter.addCategory(Intent.CATEGORY_DEFAULT);
    BaseApplication.getLocalBroadcastManager().registerReceiver(mExitReceiver, filter);
    //注册本地广播,接收程序退出广播
  }
}

接下来就是处理基类BaseFragment的问题了,这里贴出该类所有代码,具体请参考注释。


public class BaseFragment extends Fragment {
  private static final String TAG = BaseFragment.class.getSimpleName();
  protected Activity mActivity;
  //所在activity
  private String mPageName;
  //页面名
  private int mRequestCode;
  //用于startForResult的requestCode
  private CoreSwitcher mPageCoreSwitcher;
  //openPageForResult接口,用于传递返回结果
  private OnFragmentFinishListener mFragmentFinishListener;
  
  public void setFragmentFinishListener(OnFragmentFinishListener listener) {
    this.mFragmentFinishListener = listener;
  }
  
  public void setFragmentResult(int resultCode, Intent intent) {
    if (mFragmentFinishListener != null) {
      mFragmentFinishListener.onFragmentResult(mRequestCode, resultCode, intent);
    }
  }
  
  public int getRequestCode() {
    return this.mRequestCode;
  }
  
  public void setRequestCode(int code) {
    this.mRequestCode = code;
  }
  
  public boolean onKeyDown(int keyCode, KeyEvent event) {
    return false;
  }
  
  public void onFragmentDataReset(Bundle bundle) {
  }
  
  public void popToBack() {
    this.popToBack(null, null);
  }
  
  public final void popToBack(String pageName, Bundle bundle) {
    CoreSwitcher coreSwitcher = getSwitcher();
    if (coreSwitcher != null) {
      if (pageName == null) {
        coreSwitcher.popPage();
      } else {
        if (this.findPage(pageName)) {
          CoreSwitchBean page = new CoreSwitchBean(pageName, bundle);
          coreSwitcher.gotoPage(page);
        } else {
          coreSwitcher.popPage();
        }
      }
    } else {
      Log.d(TAG, "pageSwitcher null");
    }
  }
  
  public CoreSwitcher getSwitcher() {
    synchronized (BaseFragment.this) {// 加强保护,保证pageSwitcher 不为null
      if (mPageCoreSwitcher == null) {
        if (this.mActivity != null && this.mActivity instanceof CoreSwitcher) {
          mPageCoreSwitcher = (CoreSwitcher) this.mActivity;
        }
        if (mPageCoreSwitcher == null) {
          BaseActivity topActivity = BaseActivity.getTopActivity();
          if (topActivity != null && topActivity instanceof CoreSwitcher) {
            mPageCoreSwitcher = (CoreSwitcher) topActivity;
          }
        }
      }
    }
    return mPageCoreSwitcher;
  }
  public void setSwitcher(CoreSwitcher pageCoreSwitcher) {
    this.mPageCoreSwitcher = pageCoreSwitcher;
  }
  
  public boolean findPage(String pageName) {
    if (pageName == null) {
      Log.d(TAG, "pageName is null");
      return false;
    }
    CoreSwitcher coreSwitcher = getSwitcher();
    if (coreSwitcher != null) {
      return coreSwitcher.findPage(pageName);
    } else {
      Log.d(TAG, "pageSwitch is null");
      return false;
    }
  }
  
  public boolean isFragmentTop(String fragmentTag) {
    CoreSwitcher pageCoreSwitcher = this.getSwitcher();
    if (pageCoreSwitcher != null) {
      return pageCoreSwitcher.isFragmentTop(fragmentTag);
    } else {
      Log.d(TAG, "pageSwitcher is null");
      return false;
    }
  }
  
  public void onFragmentResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "onFragmentResult from baseFragment:requestCode-" + requestCode + " resultCode-" + resultCode);
  }
  
  public final Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim) {
    return this.openPage(pageName, bundle, CoreSwitchBean.convertAnimations(coreAnim), true);
  }
  
  public final Fragment openPage(String pageName, Bundle bundle, int[] anim, boolean addToBackStack) {
    return this.openPage(pageName, bundle, anim, addToBackStack, false);
  }
  
  public final Fragment openPage(String pageName, Bundle bundle, int[] anim, boolean addToBackStack, boolean newActivity) {
    if (pageName == null) {
      Log.d(TAG, "pageName is null");
      return null;
    }
    CoreSwitcher coreSwitcher = this.getSwitcher();
    if (coreSwitcher != null) {
      CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, anim, addToBackStack, newActivity);
      return coreSwitcher.openPage(page);
    } else {
      Log.d(TAG, "pageSwitcher is null");
      return null;
    }
  }
  
  public final Fragment openPage(String pageName, Bundle bundle, int[] anim) {
    return this.openPage(pageName, bundle, anim, true);
  }
  
  public final Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack) {
    return this.openPage(pageName, bundle, CoreSwitchBean.convertAnimations(coreAnim), addToBackStack, false);
  }
  
  public final Fragment openPage(String pageName, Bundle bundle, CoreAnim coreAnim, boolean addToBackStack, boolean newActivity) {
    return this.openPage(pageName, bundle, CoreSwitchBean.convertAnimations(coreAnim), addToBackStack, newActivity);
  }
  
  public Fragment gotoPage(String pageName, Bundle bundle, CoreAnim coreAnim) {
    return this.gotoPage(pageName, bundle, coreAnim,false);
  }
  
  public Fragment gotoPage(String pageName, Bundle bundle, CoreAnim coreAnim, boolean newActivity) {
    CoreSwitcher pageCoreSwitcher = this.getSwitcher();
    if (pageCoreSwitcher != null) {
      CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, coreAnim, true, newActivity);
      return pageCoreSwitcher.gotoPage(page);
    } else {
      Log.d(TAG, "pageSwitcher is null");
      return null;
    }
  }
  
  public final Fragment openPageForResult(String pageName, Bundle bundle, CoreAnim coreAnim, int requestCode) {
    return this.openPageForResult(false, pageName, bundle, coreAnim, requestCode);
  }
  
  public final Fragment openPageForResult(boolean newActivity, String pageName, Bundle bundle, CoreAnim coreAnim, int requestCode) {
    CoreSwitcher pageCoreSwitcher = this.getSwitcher();
    if (pageCoreSwitcher != null) {
      CoreSwitchBean page = new CoreSwitchBean(pageName, bundle, coreAnim, true, newActivity);
      page.setRequestCode(requestCode);
      return pageCoreSwitcher.openPageForResult(page, this);
    } else {
      Log.d(TAG, "pageSwitcher is null");
      return null;
    }
  }
  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
    mActivity = activity;
  }
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getPageName() != null) {
      Log.d(TAG, "====Fragment.onCreate====" + getPageName());
    }
  }
  public String getPageName() {
    return mPageName;
  }
  public void setPageName(String pageName) {
    mPageName = pageName;
  }
  @Override
  public void onDetach() {
    super.onDetach();
    mActivity = null;
  }
  //页面跳转接口
  public interface OnFragmentFinishListener {
    void onFragmentResult(int requestCode, int resultCode, Intent intent);
  }
}

其实无论BaseActivity还是BaseFragment的原理都是一样的,都是通过接口中的函数进行切换,最终都是调用了我们前面所说的两个核心函数。所谓的难点,其实就是openPageForResult的函数。如果调整函数中指定了新开activity,则直接调用startActivityForResult函数进行跳转,目标Activity中为BaseActivity,传递一些参数用于识别。


Intent intent = new Intent(this, BaseActivity.class);
intent.putExtra("SwitchBean", page);
intent.putExtra("startActivityForResult", "true");

然后再onCreate中获得intent


 Intent mNewIntent = getIntent();
 init(mNewIntent);

调用了init函数,在里面获得传递的两个参数,如果是startActivityForResult,则对fragment设置回调函数,当我们手动设置了setFragmentResult函数后回调就会被调用,即onFragmentResult函数回调


private void init(Intent mNewIntent) {
    try {
      CoreSwitchBean page = mNewIntent.getParcelableExtra("SwitchBean");
      String startActivityForResult = mNewIntent.getStringExtra("startActivityForResult");
      this.mFirstCoreSwitchBean = page;
      if (page != null) {
        BaseFragment fragment = null;
        boolean addToBackStack = page.isAddToBackStack();
        String pageName = page.getPageName();
        Bundle bundle = page.getBundle();
        fragment = (BaseFragment) CorePageManager.getInstance().openPageWithNewFragmentManager(getSupportFragmentManager(), pageName, bundle, null, addToBackStack);
        if (fragment != null) {
          if ("true".equalsIgnoreCase(startActivityForResult)) {
            fragment.setRequestCode(page.getRequestCode());
            fragment.setFragmentFinishListener(new BaseFragment.OnFragmentFinishListener() {
              @Override
              public void onFragmentResult(int requestCode, int resultCode, Intent intent) {
                BaseActivity.this.setResult(resultCode, intent);
              }
            });
          }
        } else {
          this.finish();
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
      Log.d(TAG, e.getMessage());
      this.finish();
    }
  }

最后的最后,也就是整个Fragment跳转框架的初始化了,继承Application编写一个应用程序类完成初始化。


public class BaseApplication extends Application {
  private static LocalBroadcastManager mLocalBroadcatManager;
  private static Context mContext;
  private static BaseApplication instance;
  public static Context getContext() {
    return mContext;
  }
  @Override
  public void onCreate() {
    super.onCreate();
    instance = this;
    mContext = this.getApplicationContext();
    CorePageManager.getInstance().init(this);
  }
  
  public void exitApp() {
    Intent intent = new Intent();
    intent.setAction(Config.ACTION_EXIT_APP);
    intent.addCategory(Intent.CATEGORY_DEFAULT);
    BaseApplication.getLocalBroadcastManager().sendBroadcast(intent);
    BaseActivity.unInit();
  }
  public static LocalBroadcastManager getLocalBroadcastManager() {
    if (mLocalBroadcatManager == null) {
      mLocalBroadcatManager = LocalBroadcastManager.getInstance(mContext);
    }
    return mLocalBroadcatManager;
  }
}

完成声明


<manifest
  package="cn.edu.zafu.corepage"
  xmlns:android="http://schemas.android.com/apk/res/android">
  <application android:allowBackup="true"
         android:label="@string/app_name"
    >
    <activity
      android:name="cn.edu.zafu.corepage.base.BaseActivity"
      android:configChanges="keyboardHidden|orientation|screenSize"
      android:exported="false"
      android:launchMode="standard"
      android:screenOrientation="portrait"
      android:theme="@style/BaseActivityTheme"
      android:windowSoftInputMode="adjustUnspecified|stateHidden" >
    </activity>
  </application>
</manifest>

之后的一切就会变得特别简单,调用即可。由于我是用android studio建的module,因此直接引用该module即可,然后提供一个程序入口Activity,该类继承BaseActivity,将该Activity声明在manifest文件中,然后我们再也不用新建Activity了,后续的页面跳转全都使用Fragment来完成。并且可以设置动画类型等一系列的参数。

使用非常简单


openPage("test1",null, CoreAnim.slide);
//打开一个页面,不传递参数第二个传null,第三个参数为动画类型,此方法有重载方法,第四个参数表示是否添加到返回栈,第五个参数表示是否新开activity,一般情况下,只需传递前三个参数即可,而动画类型,处理传递枚举类,还支持自定义的动画,对应的文件参考res/anim目录下的相应文件
openPageForResult("test2",bundle,CoreAnim.fade,requestCode);
//打开一个页面并获得返回结果,之后调用setFragmentResult和popToBack设置结果
 setFragmentResult(500, intent);
 popToBack();
重写onFragmentResult函数获取返回结果
@Override
public void onFragmentResult(int requestCode, int resultCode, Intent data) {
}
//这个使用过程同startActivityFor的整个过程

效果图:

您可能感兴趣的文章:Android基础之使用Fragment控制切换多个页面Android App中使用ViewPager+Fragment实现滑动切换效果Android Fragment中使用SurfaceView切换时闪一下黑屏的解决办法一个Activity中多个Fragment的切换Android中Fragment相互切换间不被回收的实现方法Android fragment实现多个页面切换效果Android中使用TabHost 与 Fragment 制作页面切换效果Android使用TabLayou+fragment+viewpager实现滑动切换页面效果Android开发使用Activity嵌套多个Fragment实现横竖屏切换功能的方法fragment实现隐藏及界面切换效果


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯