Fragment的產生
Android 在 Android 3.0(API 級別 11)中引入了片段,主要是為了給大屏幕(如平板電腦)上更加動態和靈活的 UI 設計提供支持。但是大多數情況下,根據不同的使用情景我們并沒有對平板應用使用fragment進行適配,所以更多的是我們把fragment作為一個可重復利用的模塊化組件,利用它擁有自身生命周期回調,布局,行為等特性來對功能模塊進行分離
Fragment的生命周期
一般來說,Activity管理Fragment已經做得非常好了,比如,鎖屏,回到屏幕的時候,Fragment都是跟隨Activity發生改變的。Fragment包含與Activity類似的生命周期,比如onCreate,onStart,onResume,onPause,onStop,onDestroy,下面是引用自Google開發者的圖片,關于一個Fragment被添加時(動態/靜態)的生命周期的變化
我們來看看fragment的生命周期都有什么作用
//系統在向 Activity 添加片段時調用的方法,附加之后才能夠通過getActivity()獲取Activity的上下文。
public void onAttach(Context context)
public void onCreate(Bundle savedInstanceState)
//返回Fragment的布局
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState)
// 在 Activity 的 onCreate() 方法已返回時調用。
public void onActivityCreated(@Nullable Bundle savedInstanceState)
//在Activity的onStart之后調用
public void onStart()
//在Activity的onResume之后調用
public void onResume()
//在Activity的onPause之前調用
public void onPause()
//在Activity的onStop之前調用
public void onStop()
// 在移除與片段關聯的視圖層次結構時調用。
public void onDestroyView()
// 在Activity的onDestroy之前調用
public void onDestroy()
// 在取消片段與 Activity 的關聯時調用。
public void onDetach()
管理片段生命周期與管理 Activity 生命周期很相似。但是因為Fragment依附于Activity,所以很容易想象,關于Fragment創建的生命周期總是在Activity之后調用,關于Fragment銷毀的生命周期總是在Activity之前調用。除此之外,在Activity需要重新創建的時候,fragment和activity一樣可以通過onSaveInstanceState來保存狀態,然后在onCreate,onCreateView或者onActivityCreated期間恢復狀態。
對于Fragment的一些操作對生命周期的影響
// replace,FragmentA替換為FragmentB,在創建完B以后會先銷毀A,再創建B的視圖
FragmentB: onAttach
FragmentB: onCreate
——————————————————————————
FragmentA: onPause
FragmentA: onStop
FragmentA: onDestroyView
FragmentA: onDestroy
FragmentA: onDetach
——————————————————————————
FragmentB: onCreateView
FragmentB: onActivityCreated
FragmentB: onStart
FragmentB: onResume
// add,添加FragmentA
FragmentA: onAttach
FragmentA: onCreate
FragmentA: onCreateView
FragmentA: onActivityCreated
FragmentA: onStart
FragmentA: onResume
//remove,移除FragmentA
FragmentA: onPause
FragmentA: onStop
FragmentA: onDestroyView
FragmentA: onDestroy
FragmentA: onDetach
//attach,從detach到attach狀態
FragmentA: onCreateView
FragmentA: onActivityCreated
FragmentA: onStart
FragmentA: onResume
//detach,從attach到detach狀態
FragmentA: onPause
FragmentA: onStop
FragmentA: onDestroyView
注意:hide和show不會觸發生命周期回調
給Fragment設置參數
使用setArguments,在創建Fragment的時候傳遞參數,然后在fragment的onCreate方法處獲取參數,但是需要注意的是setArguments()方法必須在fragment創建以后,add之前調用。這樣的好處是對Fragment的代碼進行管理
//流行一種做法是使用靜態方法newInstance()的方式來初始化Fragment
public static CircleFragmetn newInstance(String str){
Bundle bundle = new Bundle();
bundle.putString("data", str);
CircleFragmetn fragment = new CircleFragmetn();
fragment.setArguments(bundle);
return fragment;
}
沒有UI的Fragment
可以添加沒有UI的Fragment,使用add(Fragment,String)向Activity添加片段,由于它不與Activity布局的視圖關聯,所以不會觸發onCreateView。通常沒有UI的Fragment都是用于保存Activity的狀態,或者借助setTargetFragment來控制其他fragment的UI變化,使用setRetainInstance()這個方法來告訴框架,當配置發生改變的時候,嘗試保留這個fragment。SDK的APIdemos提供了范例
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.app;
import com.example.android.apis.R;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
/**
* This example shows how you can use a Fragment to easily propagate state
* (such as threads) across activity instances when an activity needs to be
* restarted due to, for example, a configuration change. This is a lot
* easier than using the raw Activity.onRetainNonConfiguratinInstance() API.
*/
public class FragmentRetainInstance extends Activity {
private static String TAG="FragmentRetainInstance";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// First time init, create the UI.
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(android.R.id.content,
new UiFragment()).commit();
}
}
/**
* This is a fragment showing UI that will be updated from work done
* in the retained fragment.
*/
public static class UiFragment extends Fragment {
RetainedFragment mWorkFragment;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_retain_instance, container, false);
// Watch for button clicks.
Button button = (Button)v.findViewById(R.id.restart);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mWorkFragment.restart();
}
});
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Check to see if we have retained the worker fragment.
mWorkFragment = (RetainedFragment)fm.findFragmentByTag("work");
// If not retained (or first time running), we need to create it.
if (mWorkFragment == null) {
mWorkFragment = new RetainedFragment();
// Tell it who it is working with.
mWorkFragment.setTargetFragment(this, 0);
fm.beginTransaction().add(mWorkFragment, "work").commit();
}
}
}
/**
* This is the Fragment implementation that will be retained across
* activity instances. It represents some ongoing work, here a thread
* we have that sits around incrementing a progress indicator.
*/
public static class RetainedFragment extends Fragment {
ProgressBar mProgressBar;
int mPosition;
boolean mReady = false;
boolean mQuiting = false;
/**
* This is the thread that will do our work. It sits in a loop running
* the progress up until it has reached the top, then stops and waits.
*/
final Thread mThread = new Thread() {
@Override
public void run() {
// We'll figure the real value out later.
int max = 10000;
// This thread runs almost forever.
while (true) {
// Update our shared state with the UI.
synchronized (this) {
// Our thread is stopped if the UI is not ready
// or it has completed its work.
while (!mReady || mPosition >= max) {
if (mQuiting) {
return;
}
try {
wait();
} catch (InterruptedException e) {
}
}
// Now update the progress. Note it is important that
// we touch the progress bar with the lock held, so it
// doesn't disappear on us.
mPosition++;
max = mProgressBar.getMax();
mProgressBar.setProgress(mPosition);
}
// Normally we would be doing some work, but put a kludge
// here to pretend like we are.
synchronized (this) {
try {
wait(50);
} catch (InterruptedException e) {
}
}
}
}
};
/**
* Fragment initialization. We way we want to be retained and
* start our thread.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Tell the framework to try to keep this fragment around
// during a configuration change.
setRetainInstance(true);
// Start up the worker thread.
mThread.start();
}
/**
* This is called when the Fragment's Activity is ready to go, after
* its content view has been installed; it is called both after
* the initial fragment creation and after the fragment is re-attached
* to a new activity.
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Retrieve the progress bar from the target's view hierarchy.
mProgressBar = (ProgressBar)getTargetFragment().getView().findViewById(
R.id.progress_horizontal);
// We are ready for our thread to go.
synchronized (mThread) {
mReady = true;
mThread.notify();
}
}
/**
* This is called when the fragment is going away. It is NOT called
* when the fragment is being propagated between activity instances.
*/
@Override
public void onDestroy() {
// Make the thread go away.
Log.d(TAG, "onDestroy");
synchronized (mThread) {
mReady = false;
mQuiting = true;
mThread.notify();
}
super.onDestroy();
}
/**
* This is called right before the fragment is detached from its
* current activity instance.
*/
@Override
public void onDetach() {
// This fragment is being detached from its activity. We need
// to make sure its thread is not going to touch any activity
// state after returning from this function.
Log.d(TAG, "onDetach");
synchronized (mThread) {
mProgressBar = null;
mReady = false;
mThread.notify();
}
super.onDetach();
}
/**
* API for our UI to restart the progress thread.
*/
public void restart() {
synchronized (mThread) {
mPosition = 0;
mThread.notify();
}
}
}
}
Fragment通過實現 onCreateOptionsMenu() 向 Activity 的應用欄添加菜單項。不過,為了使此方法能夠收到調用,您必須在 onCreate() 期間調用 setHasOptionsMenu(),以指示片段想要向選項菜單添加菜單項(否則,片段將不會收到對 onCreateOptionsMenu() 的調用)。
鎖屏帶來的問題
最近我發現了一個我們可能在平常的使用中都沒有注意到的問題,鎖屏界面對生命周期的影響,很多時候我們的Activity都會直接在onResume或者onPause等地方做一些操作,但是大部分手機都存在鎖屏界面,如果我們對鎖屏的實現比較熟悉的話都知道其實鎖屏界面也是一個Activity。如果我們的Activity處于前臺的時候,我們進入了鎖屏,那么在開啟屏幕的時候(出現鎖屏的時候),我們的Activity其實經歷了一次onRestart—onStart—onResume—onPause—onStop的生命周期變化,這是因為當我們點開屏幕的時候,首先顯示的是我們的Activity,然后系統會在我們的Activity上面打開鎖屏應用,這樣就導致我們在打開鎖屏之前,Activity就經歷了一次生命周期的變化,這一問題會留到將來學習KeyGuard的時候再進行深入研究