Android开发经验谈程序员Android技术知识

Android技术栈(一)从Activity迁移到Fragmen

2019-03-21  本文已影响175人  06fd4cf1f427

1.首先什么是Fragment?

FragmentAndroid的视图生命周期控制器,可以把它看做一个轻量级的Activity,与传统的Activity相比,它只占用更少的资源,并且提供更大的编码灵活性、在超低版本上的兼容性等.

使用Fragment,即使是在肥肠差劲的平台(例如API 19以下连ART都没有的的老系统)上也能得到较好的运行效果,并且能将过渡动画兼容到更低的版本(通过FragmentTransition指定)。

早期的Fragment出现过很多问题,比如没有onBackPressed(),没有启动模式,重复创建,辣鸡的回退栈,迷之生命周期等等,导致很多开源作者自己独立开发了用于Fragment管理的框架,其中比较出名的有YoKeyword大佬的Fragmentation.

不过事物总是曲折发展的,经过Google多年的调教,现在的Fragment的功能已经很完善了,在很多场合,足以在很多场合替代Activity的存在,上面的一些问题也得到了比较妥善的解决,如果看完这篇文章,相信你会找到答案。

image.png

巨佬JakeWharton曾经建议:一个App只需要一个Activity.

这说的就是单ActivityFragment模式.使用这种模式有许多好处:

2.要使用Fragment你必须知道的一些事情

首先要提一点,如果你要学习Fragment那么你至少得是掌握了Activity的,如果你还不了解Activity,笔者建议你先去看一些Activity相关的文章,再来进阶Fragment.从下面的文章开始,默认读者已经了解了Activity的生命周期等相关知识。

Fragment拥有Activity所有的生命周期回调函数并且由于自身特点还扩展了一些回调函数,但是这些与Activity相关的回调函数几乎只与Fragment依附的Activity有关,如果不熟悉Fragment,很容易凭直觉造成误会.例如,一个Fragment并不会因为在Fragment回退栈上有其他Fragment把它盖住,又或者是你使用FragmentTransition将它hide而导致他onPause,onPause只跟此Fragment依附的Activity有关,这在Fragment的源码中写得清清楚楚.


    /**
     * Called when the Fragment is no longer resumed.  This is generally
     * tied to {@link Activity#onPause() Activity.onPause} of the containing
     * Activity's lifecycle.
     */
    @CallSuper
    public void onPause() {
        mCalled = true;
    }

那当我们想在Fragment不显示时做一些事情要怎么办呢?我们有onHiddenChanged回调,当Fragment的显示状态通过FragmentTransition改变时(hideshow),就会回调这个函数,参数hidden将告诉你这个Fragment现在是被隐藏还是显示着.


    /**
     * Called when the hidden state (as returned by {@link #isHidden()} of
     * the fragment has changed.  Fragments start out not hidden; this will
     * be called whenever the fragment changes state from that.
     * @param hidden True if the fragment is now hidden, false otherwise.
     */
    public void onHiddenChanged(boolean hidden) {
    }

Fragment有两种方式生成,一是硬编码到xml文件中,二是在Java代码中new,然后通过FragmentManager#beginTransaction开启FragmentTransaction提交来添加Fragment(下文会介绍).两种方式存在着一定区别.硬编码到xmlFragment无法被FragmentTransition#remove移除,与Activity同生共死,所以你要是这么用了,就不用试了,移除不了的,但是在代码中new出来的是可以被移除的.

硬编码到xml中:

        <fragment
            android:id="@+id/map_view"
            android:name="org.kexie.android.dng.navi.widget.AMapCompatFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

添加Fragment的第二种方式就是使用FragmentManager#beginTransaction(代码如下)动态添加,你需要先new一个Fragment,然后通过下面Fragment#requireFragmentManager获取FragmentManager来使用beginTransaction添加Fragment,注意add方法的第一个参数,你需要给它指定一个id,也就是Fragment容器的id,通常容器是一个没有子ViewFrameLayout,它决定了Fragment要在什么位置显示。

需要注意的是FragmentTransaction并不是立即执行的,而是在当前代码执行完毕后,回到事件循环(也就是你们知道的Looper)时,才会执行,不过他会保证在下一帧渲染之前得到执行(通过Handler#createAsync机制),若要在FragmentTransaction执行时搞事情,你需要使用runOnCommit,下面的代码中我使用了Java8lambda表达式简写了Runnable.

若要使用Fragment回退栈记得addToBackStack,最后别忘了commit,这样才会生效,此时commit函数返回的是BackStackEntryid

        requireFragmentManager()
                .beginTransaction()
                .add(id, fragment)
                .runOnCommit(()->{/*TODO*/})
                .addToBackStack(null)
                .commit();

当然FragmentTransaction不止可以执行add操作,同样也可以执行remove,show,hide等操作.

这里插入一个简短的题外话作为上面知识的补充。如何在Android Studio中启用Java8?在你模块的build.gradle

android{

    //省略.....

    //加上下面的脚本代码,然后sync你的项目

        compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

onBackPressed在哪?我知道第一次使用Fragment的人肯定都超想问这个问题.众所周知Fragment本身是没有onBackPressed的.不是Google不设计,而是真的没法管理啊!!!,如果一个界面上有三四个地方都有Fragment存在,一按回退键,谁知道要交给哪个Fragment处理呢?所以Fragment是"没有"onBackPressed的.

在这里我的“没有”打了引号,因为实际上给Fragment添加类似onBackPressed的功能的办法是存在的,只是Google把它设计成交给开发者自行管理了.

要想使用FragmentonBackPressed,你可能需要先升级到AndroidX.

这里可能有人会问AndroidX是什么?

简单来讲AndroidX就是一个与平台解绑的appcompat(低版本兼容高版本功能)库,也就是说在build.gradle中不需要再与compileSdkVersion写成一样,例如之前这样的写法:

compile 'com.android.support:appcompat-v7:24.+'

(注:使用24.+则表明使用 24. 开头的版本的最新版本,若直接使用+号则表明直接使用该库的最新版本。

现在可以写成:

implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'

(注:新的依赖方式implementationcompile功能相同,但是implementation无法在该模块内引用依赖的依赖,但compile可以,这么做的好处是可以加快编译速度。新的依赖方式apicompile完全相同,只是换了名字而已)

Android Studo中的Refactor->Migrate to AndroidX的选点击之后即可将项目迁移到AndroidX,在确认的时会提示你将项目备份以免迁移失败时丢失原有项目,通常情况下不会迁移失败,只是迁移的过程会花费很多的时间,如果项目很大,迁移时间会很长,这时即使Android StudioCPU利用率为0也不要关闭, 但是如果发生迁移失败,这时候就需要手动迁移了。

一些使用gradle依赖的一些第三方库中的某些类可能继承了android.support.v4包下的Fragment,但迁移到AndroidXappcompatFragment变成了androidx.fragment.app包下,原有的代码下会画红线,Android Studio也会警告你出现错误,但是不用担心,依然可以正常编译,Android Studio在编译的时候会自动完成基类的替换,但前提是你要确保你项目里的gradle.properties进行了如下设置。

android.useAndroidX=true

android.enableJetifier=tru

为了消除这些难看的红线,你可以直接将新的Fragment使用这种方式强制转换成原有的Fragment

TextureSupportMapFragment mapFragment = TextureSupportMapFragment
.class.cast(getChildFragmentManager()
.findFragmentById(R.id.map_view));

同理,也可以将旧的Fragment强制类型转换成新的Fragment.

Fragment f = Fragment.class.cast(mapFragment);

(注:上面的TextureSupportMapFragment是一个典型案例,他是高德地图SDK中的Fragment,本身已经继承了v4包下的Fragment,可以用过上面的转换来使他兼容AndroidX

差点扯远了,搞定AndroidX后,我们就可以使用FragmentActivityaddOnBackPressedCallback方法为你的Fragment提供拦截OnBackPressed的功能了.

public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
        @NonNull OnBackPressedCallback onBackPressedCallback)

OnBackPressedCallback#handleOnBackPressed需要返回一个boolean值。如果你在这个回调里拦截了onBackPressed应该返回true,说明你自己已经处理了本次返回键按下的操作,这样你的Fragment就不会被弹出返回栈了。

值得注意的是,这个函数的第一个参数,一个LifecycleOwner,ActivityFragment都是LifecycleOwner,用于提供组件的生命周期,这个参数可以帮我们自动管理OnBackPressedCallback回调,你无需手动将他从Activity中移除,在LifecycleOwnerON_DESTROY事件来到的时候,他会被自动移除列表,你无需担心内存泄漏,框架会帮你完成这些事情。

/**
 * Interface for handling {@link ComponentActivity#onBackPressed()} callbacks without
 * strongly coupling that implementation to a subclass of {@link ComponentActivity}.
 *
 * @see ComponentActivity#addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
 * @see ComponentActivity#removeOnBackPressedCallback(OnBackPressedCallback)
 */
public interface OnBackPressedCallback {
    /**
     * Callback for handling the {@link ComponentActivity#onBackPressed()} event.
     *
     * @return True if you handled the {@link ComponentActivity#onBackPressed()} event. No
     * further {@link OnBackPressedCallback} instances will be called if you return true.
     */
    boolean handleOnBackPressed();
}

我们可以看到Activity内管理的OnBackPressedCallback的执行循序与添加时间有关.最后被添加进去的能最先得到执行.

public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
        @NonNull OnBackPressedCallback onBackPressedCallback) {
    Lifecycle lifecycle = owner.getLifecycle();
    if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
        // Already destroyed, nothing to do
        return;
    }
    // Add new callbacks to the front of the list so that
    // the most recently added callbacks get priority
    mOnBackPressedCallbacks.add(0, new LifecycleAwareOnBackPressedCallback(
            lifecycle, onBackPressedCallback));
}

可以看到它是添加到mOnBackPressedCallbacks这个List的最前面的.

Fragment是必须要有id的,使用getId可以返回自身的id,通常用这个方法返回它所在的容器的id,供其他Fragment添加进FragmentManager时使用。(比如说你使用了一个FrameLayout作为Fragment的容器,那么它就会返回那个FrameLayoutid)

    /**
     * Return the identifier this fragment is known by.  This is either
     * the android:id value supplied in a layout or the container view ID
     * supplied when adding the fragment.
     */
    final public int getId() {
        return mFragmentId;
    }

startFragmentForResult方法在哪?对不起和OnBackPressed一样,Google没有直接为我们实现这个方法,但这并不代表Fragment没有这个功能,你当然可以直接用定义getter的方式来获取Fragment上内容,但这并不是最佳实践,为了规范编码我们最好还是使用公共的API

Fragment#setTargetFragment可以给当前Fragment设置一个目标Fragment和一个请求码

public void setTargetFragment(@Nullable Fragment fragment, int requestCode)

当当前Fragment完成相应的任务后,我们可以这样将返回值送回给我们的目标Fragment通过Intent

        getTargetFragment().onActivityResult(getTargetRequestCode(),
        Activity.RESULT_OK,new Intent());

不过要注意,目标Fragment和被请求的Fragment必须在同一个FragmentManager的管理下,否则就会报错

最后,当我们在使用Fragment#getActivity()时返回的是一个可空值,如果没有判空检查在Android Studio中将会出现一个恶心的黄色警告,你可以使用requireActivity()来代替它,同样的方法还有requireFragmentManager()等.

3.Fragment生命周期

这可能是最让人懊恼的部分之一了。下面是绝对的高能区,因为接下来的那一张图,彰显了Fragment中最让人恐惧的一部分,它的生命周期.

本来笔者想要用ProcessOn,自己画一张Fragment生命周期的流程图.怎么说我都是软件工程专业的啊,最后......真香,因为这图实在是太复杂了,真要画它时间上有点过不去,所以我只好拿来主义.

下图展示了各回调发生的时间顺序:

image.png

捋一下,常用的回调有这些,觉得上面有图有点烦的话的话那就看下面总结的文字吧:

4.Fragment的替代方案

看了那么多有关Fragment的介绍,如果你还对Fragment嗤之以鼻,又想减小业务的逻辑的粒度,那么我只能给你Fragment的替代方案了。

一位square公司(对就是那个诞生了Retrofitokhttp的公司)的工程师开发的Fragment替代方案《View框架flow》,以及相关博文,国内有优秀的简书作者翻译了这篇文章《(译)我为什么不主张使用Fragment》,原作者在这篇文章中痛斥了Fragment的各种缺点,我想你可能会喜欢这个.

5.结语

好了关于从Activity迁移到Fragment的介绍差不多就到这了,我也是想到什么就写什么,所以文章的结构可能会有些乱,以后如果还有其他知识点我会慢慢补充上来.

【附】相关架构及资料
Android高级技术大纲

资料及源码领取

点赞+加群免费获取 Android IOC架构设计

领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术

上一篇下一篇

猜你喜欢

热点阅读