上一篇已經(jīng)講到了FragmentNavigator的效率問(wèn)題壮虫,因?yàn)樗谶M(jìn)行導(dǎo)航的時(shí)候是通過(guò)replace方法實(shí)現(xiàn)的咪啡,導(dǎo)致每次切換都會(huì)重新創(chuàng)建Fragment夯到。本次就要解決這個(gè)問(wèn)題峦萎。
新建一個(gè)java library 取名libnavannotation
創(chuàng)建兩個(gè)注解ActivityDestination和FragmentDestination
分別給出注解源碼:
@Target(ElementType.TYPE)
public @interface FragmentDestination {
String pageUrl();
boolean needLogin() default false;
boolean asStarter() default false;
}
@Target(ElementType.TYPE)
public @interface ActivityDestination {
String pageUrl();
boolean needLogin() default false;
boolean asStarter() default false;
}
注解就不過(guò)多的解釋了茂契,可以自行查閱資料學(xué)習(xí)蝶桶。
在新建一個(gè)java library取名 libnavcompiler,里面新建NavProcessor類(lèi)
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.shitu.libnavannotation.FragmentDestination", "com.shitu.libnavannotation.ActivityDestination"})
public class NavProcessor extends AbstractProcessor {
private static final String OUTPUT_FILE_NAME = "destnation.json";
private Messager messager;
private Filer filer;
private FileOutputStream fos;
private OutputStreamWriter writer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> fragmentElements = roundEnvironment.getElementsAnnotatedWith(FragmentDestination.class);
Set<? extends Element> activityElements = roundEnvironment.getElementsAnnotatedWith(ActivityDestination.class);
if (!fragmentElements.isEmpty() || !activityElements.isEmpty()) {
HashMap<String, JSONObject> destMap = new HashMap<>();
handDestination(fragmentElements, FragmentDestination.class, destMap);
handDestination(activityElements, ActivityDestination.class, destMap);
// app/src/main/assets
FileObject resource = null;
try {
resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "",OUTPUT_FILE_NAME );
String resourcePath = resource.toUri().getPath();
messager.printMessage(Diagnostic.Kind.NOTE, "resourcePath:" + resourcePath);
String appPath = resourcePath.substring(0, resourcePath.indexOf("app") + 4);
String assetsPath = appPath + "src/main/assets/";
File file = new File(assetsPath);
if (!file.exists()) {
file.mkdirs();
}
File outputFile = new File(file, OUTPUT_FILE_NAME);
if(outputFile.exists()){
outputFile.delete();
}
outputFile.createNewFile();
String content= JSON.toJSONString(destMap);
fos = new FileOutputStream(outputFile);
writer = new OutputStreamWriter(fos, "UTF-8");
writer.write(content);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return true;
}
private void handDestination(Set<? extends Element> elements, Class<? extends Annotation> annotationClass, HashMap<String, JSONObject> destMap) {
for (Element element : elements) {
TypeElement typeElement = (TypeElement) element;
String className = typeElement.getQualifiedName().toString();
int id = Math.abs(className.hashCode());
String pageUrl = null;
boolean needLogin = false;
boolean asStater = false;
boolean isFragment = false;
Annotation annotation = element.getAnnotation(annotationClass);
if (annotation instanceof FragmentDestination) {
FragmentDestination dest = (FragmentDestination) annotation;
pageUrl = dest.pageUrl();
needLogin = dest.needLogin();
asStater = dest.asStarter();
isFragment = true;
} else if (annotation instanceof ActivityDestination) {
ActivityDestination dest = (ActivityDestination) annotation;
pageUrl = dest.pageUrl();
needLogin = dest.needLogin();
asStater = dest.asStarter();
isFragment=false;
}
if (destMap.containsKey(pageUrl)) {
messager.printMessage(Diagnostic.Kind.ERROR, "不同的頁(yè)面不允許使用相同的pageUrl:" + className);
} else {
JSONObject object = new JSONObject();
object.put("id", id);
object.put("needLogin", needLogin);
object.put("asStarter", asStater);
object.put("pageUrl", pageUrl);
object.put("className", className);
object.put("isFragment", isFragment);
destMap.put(pageUrl,object);
}
}
}
}
代碼比較長(zhǎng)掉冶,但作用很簡(jiǎn)單真竖,就是把添加了前面申明的ActivityDestination和FragmentDestination注解的類(lèi)的注解參數(shù)解析出來(lái),然后將解析的字段通過(guò)JSON格式存儲(chǔ)到主項(xiàng)目的src/main/assets中厌小。
注意:
- NavProcessor必須集成AbstractProcessor恢共,并在類(lèi)上添加以下注解:
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.shitu.libnavannotation.FragmentDestination", "com.shitu.libnavannotation.ActivityDestination"})
注解在編譯的時(shí)候就會(huì)去執(zhí)行AbstractProcessor的子類(lèi)。
該段代碼功能簡(jiǎn)述說(shuō)明如下:
- 在init方法中初始化Filer和Message璧亚,主要用于文件的路勁和日志的處理讨韭。
- process方法中通過(guò)roundEnvironment.getElementsAnnotatedWith(CLASS)傳入注解類(lèi)型參數(shù)獲取改注解。
- 在handDestination中獲取Annotation中的參數(shù)癣蟋,將其存入HashMap透硝。
- 將HashMap中存儲(chǔ)的對(duì)象傳成json格式存入src/mian/assets/文件下。
自定義FragmentNavigator
新建FixFragmentNavigator類(lèi)疯搅,繼承FragmentNavigator濒生,重寫(xiě)FragmentNavigator中的navigate方法。將navigate方法的代碼復(fù)制到FixFragmentNavigator的navigate幔欧,并做如下改動(dòng)罪治。其實(shí)就是講replace方法改為show和hide來(lái)提供效率。具體源碼貼出:
package com.shitu.app;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.navigation.NavDestination;
import androidx.navigation.NavOptions;
import androidx.navigation.Navigator;
import androidx.navigation.fragment.FragmentNavigator;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Map;
@Navigator.Name("fixfragment")
public class FixFragmentNavigator extends FragmentNavigator {
private static final String TAG = "FixFragmentNavigator";
private Context mContext;
private FragmentManager mManager;
private int mContainerId;
public FixFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) {
super(context, manager, containerId);
mContainerId = containerId;
mManager = manager;
mContext = context;
}
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
// final Fragment frag = instantiateFragment(mContext, mManager,
// className, args);
// frag.setArguments(args);
final FragmentTransaction ft = mManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
Fragment fragment = mManager.getPrimaryNavigationFragment();
if (fragment != null) {
ft.hide(fragment);
}
Fragment frag = null;
String tag = String.valueOf(destination.getId());
frag = mManager.findFragmentByTag(tag);
if (frag != null) {
ft.show(frag);
} else {
frag = instantiateFragment(mContext, mManager,
className, args);
frag.setArguments(args);
ft.add(mContainerId, frag, tag);
}
// ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
ArrayDeque<Integer> mBackStack = null;
try {
Field field = FragmentNavigator.class.getDeclaredField("mBackStack");
field.setAccessible(true);
mBackStack = (ArrayDeque<Integer>) field.get(this);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
private String generateBackStackName(int backStackindex, int destId) {
return backStackindex + "-" + destId;
}
}
最后需要將FixFragmentNavigator添加到NavigatorProvider中替換原有的FragmentNavigator琐馆,
所以新建一個(gè)NavGraphBuilder類(lèi)规阀,提供公共靜態(tài)方法build.
public static void build(NavController controller, FragmentActivity activity, int containerId) {
NavigatorProvider provider = controller.getNavigatorProvider();
FixFragmentNavigator fragmentNavigator = new FixFragmentNavigator(activity, activity.getSupportFragmentManager(), containerId);
provider.addNavigator(fragmentNavigator);
ActivityNavigator activityNavigator = provider.getNavigator(ActivityNavigator.class);
還記得我們?cè)谇懊娑x了兩個(gè)注解,然后通過(guò)編譯時(shí)將作用在Fragment和Activity上的注解的參數(shù)獲取到存儲(chǔ)在assest文件下嗎瘦麸?現(xiàn)在需要將這個(gè)json格式的內(nèi)容轉(zhuǎn)成一個(gè)Destination對(duì)象谁撼,然后將Destination加入到NavGraph中,看看源碼:
HashMap<String, Destination> destConfig = AppConfig.getDestConfig();
NavGraph navGraph = new NavGraph(new NavGraphNavigator(provider));
for (Destination value : destConfig.values()) {
if (value.isFragment) {
FragmentNavigator.Destination destination = fragmentNavigator.createDestination();
destination.setId(value.id);
destination.setClassName(value.className);
destination.addDeepLink(value.pageUrl);
navGraph.addDestination(destination);
} else {
ActivityNavigator.Destination destination = activityNavigator.createDestination();
destination.setId(value.id);
destination.addDeepLink(value.pageUrl);
navGraph.addDestination(destination);
destination.setComponentName(new ComponentName(AppGlobals.getApplication().getPackageName(), value.className));
}
if (value.asStarter) {
navGraph.setStartDestination(value.id);
}
}
controller.setGraph(navGraph);
}
上面的代碼也比較簡(jiǎn)單,會(huì)判斷是fragment還是activity, fragment是可以構(gòu)建fragment實(shí)例啟動(dòng)滋饲,activity則是通過(guò)Intent啟動(dòng)厉碟。
基本的代碼改造已經(jīng)結(jié)束,使用也很簡(jiǎn)單屠缭,在fragment上寫(xiě)上自定義的注解箍鼓,例如:
在MainActivity中使用上面的代碼。
然后給BottomNavigationView寫(xiě)上點(diǎn)擊監(jiān)聽(tīng)呵曹, navView.setOnNavigationItemSelectedListener(this);
實(shí)現(xiàn)監(jiān)聽(tīng)方法進(jìn)行導(dǎo)航處理
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
navController.navigate(menuItem.getItemId());
return !TextUtils.isEmpty(menuItem.getTitle());
}
這樣款咖,F(xiàn)ragment不用再每次切換是都重新創(chuàng)建了何暮,不信可以自己打印日志試一試哦。