巨佬JakeWharton曾经建议:一个App只需要一个Activity.
这说的就是单Activity多Fragment模式.使用这种模式有许多好处:
- 首先第一个好处就是流畅,要知道
Activity属于系统组件,受AMS管理并且自身是一个God Object,它的开销是很大的,单Activity模式可以为我们节省很多资源,还可以避免资源不足时,被前台Activity覆盖的Activity被杀掉导致页面数据丢失的情况(因为只有一个Activity,除非JAVA堆内存到达系统要杀掉一个程序的临界点,否则系统最不倾向于杀死前台正在运行的Activity); - 其次就是可以将业务逻辑拆分成更小的模块,并将其组合复用,这在这在大型软件系统中尤为重要(新版
知乎就使用了单Activity多Fragment这种模式),因为我们都知道Activity的是无法在多个页面中复用的,而此时Fragment就有了它的勇武之地,它作为轻量级的Activity,基本可以代理Activity的工作,并且他是可复用 - 再者,使用
Fragment可以为程序带来更大的灵活性,我们都知道在Activity之间传递对象,对象需要序列化,这是因为Activity作为系统组件,是受AMS管理的,而AMS属于系统进程,不在当前程序运行的进程中,启动Activity时需要暂时离开当前进程去到AMS的进程中,而AMS则会将你准备好的数据(也就是Intent之类的)用来启动Activity,这也是Fragment和Activity之间的区别之一,Activity属于系统组件,可以在别的进程运行(组件化/多进程方案),而Fragment只是框架提供给我们的的一个组件,它必须依附于Activity生存,并且只能在当前进程使用,但这同时也意味这它可以获得更大的灵活性,我们可以给Fragment传递对象而无需序列化,甚至可以给Fragment传递View之类的对象,这都是Activity不容易做到的.
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
br/>*/
@CallSuper
mCalled = true;
}
那当我们想在Fragment不显示时做一些事情要怎么办呢?我们有onHiddenChanged回调,当Fragment的显示状态通过FragmentTransition改变时(hide和show),就会回调这个函数,参数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(下文会介绍).两种方式存在着一定区别.硬编码到xml的Fragment无法被FragmentTransition#remove移除,与Activity同生共死,所以你要是这么用了,就不用试了,移除不了的,但是在代码中new出来的是可以被移除的.
硬编码到xml中:
<fragmentandroid:id="@+id/map_view"
br/>android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
添加Fragment的第二种方式就是使用FragmentManager#beginTransaction(代码如下)动态添加,你需要先new一个Fragment,然后通过下面Fragment#requireFragmentManager获取FragmentManager来使用beginTransaction添加Fragment,注意add方法的第一个参数,你需要给它指定一个id,也就是Fragment容器的id,通常容器是一个没有子View的FrameLayout,它决定了Fragment要在什么位置显示。
需要注意的是FragmentTransaction并不是立即执行的,而是在当前代码执行完毕后,回到事件循环(也就是你们知道的Looper)时,才会执行,不过他会保证在下一帧渲染之前得到执行(通过Handler#createAsync机制),若要在FragmentTransaction执行时搞事情,你需要使用runOnCommit,下面的代码中我使用了Java8的lambda表达式简写了Runnable.
若要使用Fragment回退栈记得addToBackStack,最后别忘了commit,这样才会生效,此时commit函数返回的是BackStackEntry的id
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把它设计成交给开发者自行管理了.
要想使用Fragment的onBackPressed,你可能需要先升级到AndroidX.
这里可能有人会问AndroidX是什么?
简单来讲AndroidX就是一个与平台解绑的appcompat(低版本兼容高版本功能)库,也就是说在build.gradle中不需要再与compileSdkVersion写成一样,例如之前这样的写法:
compile 'com.android.support:appcompat-v7:24.+'
(注:使用24.+则表明使用 24. 开头的版本的最新版本,若直接使用+号则表明直接使用该库的最新版本。
现在可以写成:
implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
(注:新的依赖方式implementation与compile功能相同,但是implementation无法在该模块内引用依赖的依赖,但compile可以,这么做的好处是可以加快编译速度。新的依赖方式api与compile完全相同,只是换了名字而已)
在Android Studo中的Refactor->Migrate to AndroidX的选点击之后即可将项目迁移到AndroidX,在确认的时会提示你将项目备份以免迁移失败时丢失原有项目,通常情况下不会迁移失败,只是迁移的过程会花费很多的时间,如果项目很大,迁移时间会很长,这时即使Android Studio的CPU利用率为0也不要关闭, 但是如果发生迁移失败,这时候就需要手动迁移了。
一些使用gradle依赖的一些第三方库中的某些类可能继承了android.support.v4包下的Fragment,但迁移到AndroidX后appcompat的Fragment变成了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后,我们就可以使用FragmentActivity的addOnBackPressedCallback方法为你的Fragment提供拦截OnBackPressed的功能了.
public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback)
OnBackPressedCallback#handleOnBackPressed需要返回一个boolean值。如果你在这个回调里拦截了onBackPressed应该返回true,说明你自己已经处理了本次返回键按下的操作,这样你的Fragment就不会被弹出返回栈了。
值得注意的是,这个函数的第一个参数,一个LifecycleOwner,Activity和Fragment都是LifecycleOwner,用于提供组件的生命周期,这个参数可以帮我们自动管理OnBackPressedCallback回调,你无需手动将他从Activity中移除,在LifecycleOwner的ON_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
最后
针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
-
Android前沿技术大纲

-
全套体系化高级架构视频

**Android高级架构资料、源码、笔记、视频

。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。**










