jetpack系列之自定義FragmentNavigator

上一篇已經(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

image.png

創(chuàng)建兩個(gè)注解ActivityDestination和FragmentDestination


image.png

分別給出注解源碼:

@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)


image.png
@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中厌小。

注意:

  1. 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ě)上自定義的注解箍鼓,例如:


image.png
image.png

在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)建了何暮,不信可以自己打印日志試一試哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铐殃,一起剝皮案震驚了整個(gè)濱河市海洼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌富腊,老刑警劉巖坏逢,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赘被,居然都是意外死亡是整,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)民假,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浮入,“玉大人,你說(shuō)我怎么就攤上這事阳欲《嬗” “怎么了陋率?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵球化,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我瓦糟,道長(zhǎng)筒愚,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任菩浙,我火速辦了婚禮巢掺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘劲蜻。我一直安慰自己陆淀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布先嬉。 她就那樣靜靜地躺著轧苫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疫蔓。 梳的紋絲不亂的頭發(fā)上含懊,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音衅胀,去河邊找鬼岔乔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滚躯,可吹牛的內(nèi)容都是我干的雏门。 我是一名探鬼主播嘿歌,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茁影!你這毒婦竟也來(lái)了搅幅?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤呼胚,失蹤者是張志新(化名)和其女友劉穎茄唐,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蝇更,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沪编,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了年扩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚁廓。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖厨幻,靈堂內(nèi)的尸體忽然破棺而出相嵌,到底是詐尸還是另有隱情,我是刑警寧澤况脆,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布饭宾,位于F島的核電站,受9級(jí)特大地震影響格了,放射性物質(zhì)發(fā)生泄漏看铆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一盛末、第九天 我趴在偏房一處隱蔽的房頂上張望弹惦。 院中可真熱鬧,春花似錦悄但、人聲如沸棠隐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)助泽。三九已至,卻和暖如春净嘀,著一層夾襖步出監(jiān)牢的瞬間报咳,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工挖藏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留暑刃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓膜眠,卻偏偏與公主長(zhǎng)得像岩臣,于是被迫代替她去往敵國(guó)和親溜嗜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359