Android-Fragment的生命周期懒加载xiao习
上一篇我们相对以前较为深点的认识了下Fragment的生命周期回调函数Android-Fragment的生命周期较详细理解实践 - 还得琢磨琢磨。
1. 再提及下其中的onDestroyView() - > onCreateView()这个周期是我们靠back stack来模拟的,就相当于是我们手动管理fragment的栈,这个目前小白还没接触过这种实际的项目实践(或许以后做重叠什么的会用到,先提及下,加深点印象)。
2. onSaveInstanceState -onViewStateRestored 这个小白没有做演示哈,这个有兴趣可以搞搞。某些特殊情况下,应该是需要这样做下兼容处理。然后剩下就是我们懒加载的处理。
我们先来看下setUserVisibleHint、onViewCreated、onCreate这些个关键的周期回调函数(懒加载需要在这里处理一些个东东),都来打印下看看:
1. 先搞个HomeActivity,加载下fragment吧....如果初学不太懂fragment,或者像小白一样都忘记了...我们看文档嘛!
1.1 记得第一步么,activity需要继承FragmentActivity,目前我们是继承兼容性的AppCompatActivity,间距再次继承了FragmentActivity。。。
image1.2 这个时候就要看另外一个fragmentmanager了 FragmentManager | Android Developers
看下公共的方法
image image image image第一个就是最熟悉的FragmentTransaction, 另外还可以获取fragmentmanager管理的fragment列表,以及进行back stack的pop等操作(上一篇我们有说过一个pop的操作会导致走onDestroyView和onCreateView,但是不会重新onCreate...)。findFragmentByTag也是一个关键,我们操作碎片的时候可以用该标记tag查找该碎片 - 如果不这样标记那么,我们就只能获取列表然后遍历超找了(找到了然后利用instaceof来判断类型啥的....)
大部分都是辅助性的方法,不过小白看到一个putFragment - 一开始我还以为是添加碎片的,但是发现并没有添加到某个容器的这样的参数。实际上这个是存储fragment的引用到bundle中,以便于后面可以用getFragment(Bundle, String)获取后进行恢复。
putFragment
added in version 22.1.0
void putFragment (Bundle bundle,
String key,
Fragment fragment)
Put a reference to a fragment in a Bundle. This Bundle can be persisted as saved state, and when later restoring getFragment(Bundle, String) will return the current instance of the same fragment.
Parameters
bundle Bundle: The bundle in which to put the fragment reference.
key String: The name of the entry in the bundle.
fragment Fragment: The Fragment whose reference is to be stored.
真正的平时我们添加显示隐等操作还是这个转换器FragmentTransaction, 所以可以重点看下这个转换器如何使用:
1. 首先有几个常量
image这个不太懂,从字面意思能看点。但是具体使用就不行了。不过搜了下其中 setTransition可以设置标准的一个动画,打开动画,关闭动画,渐变动画等, 没玩过,呜呜~~~:
image至于别的,相信随着深入会慢慢接触到。接下来直接看提供的方法吧:
Public methods
abstract FragmentTransaction add(Fragment fragment, String tag)
Calls add(int, Fragment, String) with a 0 containerViewId.
abstract FragmentTransaction add(int containerViewId, Fragment fragment, String tag)
Add a fragment to the activity state.
abstract FragmentTransaction add(int containerViewId, Fragment fragment)
Calls add(int, Fragment, String) with a null tag.
abstract FragmentTransaction addToBackStack(String name)
Add this transaction to the back stack.
abstract int commit()
Schedules a commit of this transaction.
abstract int commitAllowingStateLoss()
Like commit() but allows the commit to be executed after an activity's state is saved.
abstract void commitNow()
Commits this transaction synchronously.
abstract void commitNowAllowingStateLoss()
Like commitNow() but allows the commit to be executed after an activity's state is saved.
abstract FragmentTransaction hide(Fragment fragment)
Hides an existing fragment.
abstract FragmentTransaction replace(int containerViewId, Fragment fragment, String tag)
Replace an existing fragment that was added to a container.
abstract FragmentTransaction replace(int containerViewId, Fragment fragment)
Calls replace(int, Fragment, String) with a null tag.
abstract FragmentTransaction setTransition(int transit)
Select a standard transition animation for this transaction.
abstract FragmentTransaction show(Fragment fragment)
Shows a previously hidden fragment.
方法没有全部列完。有些方法是没就见过了的。上面就列了几个可能会经常使用的,add - 看就知道是添加碎片到容器的啦 hide - 隐藏碎片 show - 显示碎片 commit - 设置提交生效,比如添加了一个碎片需要提交方可生效
**add **有三个方法,分别是可以带tag,可以附着到某个布局容器,可以不带tag,也可以不用附着到某个布局容器,直接add到某个activity(这种小白理解就是我们动态创建了一个布局,没有采用xml的形式,此时获取该界面的碎片管理器fragmentmanager,这个时候就直接添加就可以了。)
**commit **- Schedules a commit of this transaction.就是某个转换操作提交生效(提交的方式也分了很多种,有异步的,还有运行保存状态以后再执行提交commitNowAllowingStateLoss等)。总之一脸懵逼,不懂呀! 这个应该是要看源码的,小白就去度了下这个方法 Fragment进阶-commit使用细节及源码分析
image看来都是有用的,只是我们没有涉及到或者说我们写的东西不够健壮,所以根本就没有考虑到。
按理来讲,我们HomeActivity退出应该是需要清楚碎片的,貌似小白一只没有怎么去关注这个东西。一个是碎片了解太少,一个也是深度远远不够。 - 如果你做碎片释放,可能就会有一些问题,或者就用到了一些个陌生的处理方式。人菜就得多看 ,哇咔咔....
然后就是hide, show,这个相对还好理解,平时可能用的还蛮多的。如果我们不想切换页面时每次都重新创建,我们就可以判断是否为null,是否已经添加到fragmentmanager,然后如果已经add了,就hide之前的,然后show当前的即可!小白目前是这样的逻辑。
看下replace, 这个也是可能经常用。有时候主页导航页的时候想每次都重新创建页面,我们一般就每次切换页面都replace掉上一个页面, 这个方法也可以带tag标识,方便查找。
小白的项目大概就是酱紫
image我们做一个最简单的fragment的使用吧:
package com.example.hl;
import android.net.Uri;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.example.lieyun_android.myapplication.R;
public class HomeActivity extends AppCompatActivity implements FragmentFirst.OnFragmentInteractionListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.ah_fragmentContent, FragmentFirst.newInstance("", ""));
fragmentTransaction.commit();
}
@Override
public void onFragmentInteraction(Uri uri) {
}
}
package com.example.hl;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.lieyun_android.myapplication.R;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link FragmentFirst.OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link FragmentFirst#newInstance} factory method to
* create an instance of this fragment.
*/
public class FragmentFirst extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private OnFragmentInteractionListener mListener;
public FragmentFirst() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment FragmentFirst.
*/
// TODO: Rename and change types and number of parameters
public static FragmentFirst newInstance(String param1, String param2) {
FragmentFirst fragment = new FragmentFirst();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("test", "onCreate");
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.e("test", "onCreateView");
return inflater.inflate(R.layout.fragment_fragment_first, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.e("test", "onViewCreated");
}
// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.e("test", "onAttach context=" + (context.getClass().getComponentType()));
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
Log.e("test", "onDetach");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("test", "onDestroy");
}
@Override
public void onResume() {
super.onResume();
Log.e("test", "onResume");
}
@Override
public void onStop() {
super.onStop();
Log.e("test", "onStop");
}
@Override
public void onPause() {
super.onPause();
Log.e("test", "onPause");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.e("test", "onDestroyView");
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.e("test", "setUserVisibleHint isVisibleToUser=" + isVisibleToUser);
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
Log.e("test", "onHiddenChanged hidden=" + hidden);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
///< EditText etCon
//String content = etCon.getText().toString();
//outState.putString("inputmsg", content);
Log.e("test", "onSaveInstanceState outState=" + outState);
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
Log.e("test", "onViewStateRestored savedInstanceState=" + savedInstanceState);
if(savedInstanceState != null){
///< EditText etCon
//etCon.setText(savedInstanceState.getString("inputmsg", ""));
}
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
}
activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.hl.HomeActivity">
<FrameLayout
android:id="@+id/ah_fragmentContent"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
基本就可以显示了
image当我们用as创建一个fragment的时候,一般会有如下的方法(内部又有OnFragmentInteractionListener的回调保存)
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.e("test", "onAttach context=" + (context.getClass().getComponentType()));
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
这就是当碎片附着到activity的时候会被调用,此时activity需要继承该接口,并实现,当碎片想要操作activity界面时可以通过该接口进行回调,进而实现交互。比如fragment页面点胶机某个按钮或者文本想要进行跳转,弹窗等操作时就可以调用该方法,同时传递一个uri,activity根据uri进行事件类别解析和处理。
早期的时候一般创建fragment还没有这样的设计.想的越来越周到了...
然后我加点点击事件,做点fragment碎片replace、hide/show, setUserVisibleHint的一些操作,看下生命周期的变化...
imagesetUserVisibleHint操作
image此时触发setUserVisibleHint生命周期回调
hide/show操作
public void onClickB(View view) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (view.getId() == R.id.ah_fragmentBottoma){
fragmentTransaction.hide(fragmentFirstb);
fragmentTransaction.show(fragmentFirsta);
}else if (view.getId() == R.id.ah_fragmentBottomb){
fragmentTransaction.hide(fragmentFirsta);
fragmentTransaction.show(fragmentFirstb);
}
fragmentTransaction.commit();
}
此时触发onHiddenChanged回调
relace则触发onDestroyView, onDestory,onDetach等常规周期回调
image我们只有通过调用碎片的setUserVisibleHint方法可以触发setUserVisibleHint的回调。实际过程我们的Viewpaper和Fragment的联合使用时,也是会有该周期的回调。
那我们的懒加载改怎么搞了。虽然我们知道 1. 首先我们要保存视图,进行视图复用。 2. 其次进行数据更新(进而更新视图)
1. 这个我们就是在onViewCreated中拿到视图就可以保存了,没有问题
private View viewFg = null;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
if (null == viewFg){
viewFg = view;
}
super.onViewCreated(viewFg, savedInstanceState);
Log.e("test", mParam1 + " onViewCreated");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.e("test", mParam1 + " onCreateView mParam1=" + mParam1);
if (null != viewFg){
return viewFg;
}
return inflater.inflate(R.layout.fragment_fragment_first, container, false);
}
以上就是直接使用视图了,避免了重复创建的问题。
2. 然后在setUserVisibleHint中判断如果界面可见,则进行数据的加载
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser){ ///< 界面可见时
///< TODO 准备数据,请求网络等
mParam1 += "你妹的!";
if (null != viewFg){
TextView tv = viewFg.findViewById(R.id.fff_tv);
tv.setText(mParam1);
}
}
Log.e("test", mParam1 + " setUserVisibleHint isVisibleToUser=" + isVisibleToUser);
}
简单的逻辑可能就是这样。上一篇小白贴出来的懒加载的案例判断比较多,可能有点难理解,它在setUserVisibleHint做了判断,处理,最终目的就是防止首次启动多次加载的问题。
image<figcaption style="margin-top: 0.66667em; padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(153, 153, 153);">判断null就是因为setUserVisibleHint创建时会先调用一次</figcaption>
所以,如果有特殊的需求和bug,尽量结合相关知识进行逻辑的处理和完善,基本上小白和很多不会的人应该也可以自己封装了!
小白看了另外一篇讲的相对清晰 fragment懒加载终极封装!! - zzq的博客 - CSDN博客
我看有些加载只是说界面可见进行数据请求加载,并没有进行布局复用。看来每个人的使用需求不同了哟。。。。
这篇比较正规哟。。不过仔细看还是继续了视图的创建。 我之前项目就是看的这篇 【Android】再来一篇Fragment懒加载(只加载一次哦) - 请叫我大苏 - 博客园
总之还是实现可见加载数据,并看是否需要每次更新数据, 然后实现布局复用和视图更新。基本上懒加载就满足需求了咯!!!
东看看,西看看,貌似自己就可以搞搞简单的懒加载了....嘿嘿。。发现自己好菜呀。只是了解了一些个生命周期,剩下的还得多实践,多搞搞。关键估计还是这个源码呀。。。。是需要加快进程,尽快把源码专栏走起来了....