Android Jetpack Architecture原理之ViewModel

ncmon發表於2018-10-08


  Jetpack已經出了很久很久了,近幾年的GDD幾乎每次都會介紹新的元件,說來慚愧,一直沒有好好學習,看近年的Google 的很多Demo中其實都有所體現,之前都是大概的瞭解了一遍。最近決定,好好梳理一遍,既學習其用法,也嘗試學習下其設計思想。也是時候該補充一下了。進入正題--ViewModel

  首先都是看官方的例子,ViewModel官方的的例子是會和另一個架構庫LiveData寫在一起,很多的部落格也是照官方的例子來說明,開始接觸時甚至給了我一種假象:ViewModel都是和LiveData一起使用的。後來閱讀才瞭解,ViewModel和LiveData職責分工還是很明顯的,使用LiveData Demo主要使用其observe功能,LiveDate的使用及原理之後再分析,甚至在appcompat-v7:27.1.1中直接單獨整合了ViewModel.所以,故為排除干擾,今天不會使用官方的主流Demo用法,先來看ViewModel。

  Android的UI控制器(Activity和Fragment)從建立到銷燬擁有自己完整的生命週期,當系統配置發生改變時((Configuration changes)),系統就會銷燬Activity和與之關聯的Fragment然後再次重建(可通過在AndroidManifast.xml中配置android:configChanges修改某些行為,這裡不討論),那麼儲存在當前UI中的臨時資料也會被清空,例如,登陸輸入框,輸入賬號或密碼後旋轉螢幕,檢視被重建,輸入過的資料也清空了,這無疑是一種不友好的使用者體驗。對於少量的可序列化資料可以使用onSaveInstanceState()方法儲存然後在onCreate()方法中重新恢復,正如所說onSaveInstanceState對於大量的資料快取有一定的侷限性,大量的資料快取則可以使用Fragment.setRetainInstance(true)來儲存資料。ViewModel也是提供了相同的功能,其實和“RetainInstance”也有關聯,用來儲存和管理與UI相關的資料,允許資料在系統配置變化後存活,我們一起看一下這個ViewModel的快取是怎麼實現的呢?

使用方式

首先先看下使用方式,先上效果圖

ViewMode Sample

public class MyViewModel extends ViewModel {
   	String name;
    public String getName(){
    	return name;
    }
    public void setName(String name){
    	this.name = name;
    }
    @Override
    protected void onCleared() {
    	super.onCleared();
    	name = null;
    }
}
複製程式碼

 

public class ViewModelActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "ViewModelActivity";
    TextView textView;
    private MyViewModel myViewModel;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewmodel);
        textView = findViewById(R.id.textView);
        textView.setOnClickListener(this);
        ViewModelProvider.Factory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication());
	/*
	*這裡的this是ViewModelStoreOwner介面在appcompat-v7:27.1.1支援庫中AppCompatActivity已經實現了,		
	*如果是較低版本,需要更新支援包或者參考其實現對本來繼承的Activity做對應實現。
	*/
        ViewModelProvider provider = new ViewModelProvider(this, factory);//
        myViewModel = provider.get(MyViewModel.class);
        Log.e(TAG, "onCreate: " + myViewModel.getName() );
        if (myViewModel.getName() != null) {
            textView.setText(myViewModel.getName());
        }
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.textView:
                myViewModel.setName("MyViewModel Test");
                textView.setText(myViewModel.getName());
                break;
        }
    }
}
複製程式碼

 

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="default"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
複製程式碼

非常簡單的一個例子,這就是ViewModel最簡單的使用了,就是TextView中顯示ViewModel的資料。ViewModel需要由ViewModelProvider.get(Class)來取得,旋轉螢幕銷燬後,之前改變的資料還在。
發現的一些疑問
接下來就是進入主題分析下ViewModel到底是怎麼實現的呢?
帶著問題看原始碼:

  • ViewModelProvider是幹啥的?
  • AndroidViewModelFactory 這命名一看就是應該是工廠模式,工廠建立了什麼?
  • provider.get(MyViewModel.class) 這裡直接使用的get命名就得到了需要的唯一資料
  • 註釋中ViewModelStoreOwner又是什麼角色?
    原始碼分析 先看ViewModel類,沒什麼說的,就是一個麼有任何真正實現的抽象類,只有一個抽象方法onCleared()
  public abstract class ViewModel {
      /**
       * This method will be called when this ViewModel is no longer used and will be destroyed.
       * <p>
       * It is useful when ViewModel observes some data and you need to clear this subscription to
       * prevent a leak of this ViewModel.
       */
      @SuppressWarnings("WeakerAccess")
      protected void onCleared() {
      }
  }
複製程式碼

接著看下ViewModelFactory,顧名思義就是製造ViewModel的。
AndroidViewModelFactory的繼承關係如下:

android.arch.lifecycle.ViewModelProvider.Factory

android.arch.lifecycle.ViewModelProvider.NewInstanceFactory

android.arch.lifecycle.ViewModelProvider.AndroidViewModelFactory

Factory是一個只包含一個create的interface,NewInstanceFactory實現了該方法傳入Class會利用ViewModel的預設無參構造器建立一個對應ViewModel的例項,而AndroidViewModelFactory增加了一個屬性就是應用的Applicaion,同時重寫create方法,檢視ViewModel是否有包含Applicaion引數的構造方法從而使用,對應的其實還有一個AndroidViewModel是ViewModel的子類,預設已經實現了帶有Application引數的構造方法,需要使用在ViewModel中使用application的直接繼承AndroidViewModel就可以,看到這裡其實最上面的例子有個不是問題的問題,其實上面的Factory直接使用NewInstanceFactory就可以建立出對應的ViewModel例項了。

	/**
	 * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
	 */
	public interface Factory {
	    /**
	     * Creates a new instance of the given {@code Class}.
	     * <p>
	     *
	     * @param modelClass a {@code Class} whose instance is requested
	     * @param <T>        The type parameter for the ViewModel.
	     * @return a newly created ViewModel
	     */
	    @NonNull
	    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
	}

複製程式碼
/**
 * Simple factory, which calls empty constructor on the give class.
 */
public static class NewInstanceFactory implements Factory {

    @SuppressWarnings("ClassNewInstance")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        }
    }
}
複製程式碼
/**
 * {@link Factory} which may create {@link AndroidViewModel} and
 * {@link ViewModel}, which have an empty constructor.
 */
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static AndroidViewModelFactory sInstance;

    /**
     * Retrieve a singleton instance of AndroidViewModelFactory.
     *
     * @param application an application to pass in {@link AndroidViewModel}
     * @return A valid {@link AndroidViewModelFactory}
     */
    @NonNull
    public static AndroidViewModelFactory getInstance(@NonNull Application application) {
        if (sInstance == null) {
            sInstance = new AndroidViewModelFactory(application);
        }
        return sInstance;
    }

    private Application mApplication;

    /**
     * Creates a {@code AndroidViewModelFactory}
     *
     * @param application an application to pass in {@link AndroidViewModel}
     */
    public AndroidViewModelFactory(@NonNull Application application) {
        mApplication = application;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
        return super.create(modelClass);
    }
}
複製程式碼

之後通過ViewModelStoreOwner和剛剛建立的Factory建立出ViewModelPrivider例項

/**
 * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
 * {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
 *
 * @param owner   a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
 *                retain {@code ViewModels}
 * @param factory a {@code Factory} which will be used to instantiate
 *                new {@code ViewModels}
 */
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

/**
 * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
 * {@code Factory} and retain them in the given {@code store}.
 *
 * @param store   {@code ViewModelStore} where ViewModels will be stored.
 * @param factory factory a {@code Factory} which will be used to instantiate
 *                new {@code ViewModels}
 */
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    this.mViewModelStore = store;
}
複製程式碼
/**
 * A scope that owns {@link ViewModelStore}.
 * <p>
 * A responsibility of an implementation of this interface is to retain owned ViewModelStore
 * during the configuration changes and call {@link ViewModelStore#clear()}, when this scope is
 * going to be destroyed.
 */
@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}
複製程式碼

ViewModelStoreOwner 也是一個介面是FragmentActivity實現了該介面並實現了其中的getViewModelStore()方法

public class FragmentActivity extends BaseFragmentActivityApi16 implements
        ViewModelStoreOwner...{
    private ViewModelStore mViewModelStore;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
    }
    /**
     * Returns the {@link ViewModelStore} associated with this activity
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }
}
複製程式碼

這個ViewModelStore又是什麼呢,其實就是真正利用HashMap儲存ViewModel的地方了,看下程式碼在儲存和clear同時會呼叫ViewModel需要實現的抽象方法onClear()

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}
複製程式碼

這樣ViewModelProvider就是有了一個ViewModel的容器,這時去呼叫ViewModelProvider的get(Class)方法就是去呼叫mViewModelStore 的get()方法取出對應的ViewModel所以這裡只要持有的ViewModelStore是有快取的,那麼取出的ViewModel就是相同的快取了。

/**
 * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
 * an activity), associated with this {@code ViewModelProvider}.
 * <p>
 * The created ViewModel is associated with the given scope and will be retained
 * as long as the scope is alive (e.g. if it is an activity, until it is
 * finished or process is killed).
 *
 * @param modelClass The class of the ViewModel to create an instance of it if it is not
 *                   present.
 * @param <T>        The type parameter for the ViewModel.
 * @return A ViewModel that is an instance of the given type {@code T}.
 */
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

複製程式碼

看到這裡就會發現ViewModelStore的快取其實是通過NonConfigurationInstances的快取來實現的,這樣就完成了Activity銷燬重建後ViewModel還儲存原來的資料的過程,那麼NonConfigurationInstances 是什麼呢?如果有了解過使用在Activity中使用onRetainNonConfigurationInstance()儲存快取資料,在onCreate()中通過getLastNonConfigurationInstance()恢復之前的資料狀態的同學可能會很熟悉這裡的寫法,是的,這裡FragmentActivity就是使用的這種方式來儲存之前的ViewModelStore,看下FragmentActivity的onRetainNonConfigurationInstance()方法。

    /**
     * Retain all appropriate fragment state.  You can NOT
     * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
     * if you want to retain your own state.
     */
    @Override
    public final Object onRetainNonConfigurationInstance() {
        if (mStopped) {
            doReallyStop(true);
        }

        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;//就是這裡了,會把之前的VeiwmodelStroe儲存到NonConfigurationInstances中以供後續恢復使用
        nci.fragments = fragments;
        return nci;
    }
複製程式碼

這裡其實再次出現了一個問題 onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()又是怎麼恢復資料呢?...這個其實和Activity的啟動流程相關,這裡也介紹一下吧,之後的內容其實是Activity的內容了,趁這次看ViwModel也跟著看了一遍,有了解過Activity啟動流程的同學更容易理解的多,大家酌情觀看。

也不能從頭開始說起,再從頭就要越扯越遠了,就從ActivityThread.java中的scheduleLaunchActivity開始

        @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
        }
複製程式碼

從ActivityThread.java中H(extents Handler)接收到LAUNCH_ACTIVITY,並且會接收ActivityClientRecord,其中會呼叫ActivityThread的handleLaunchActivity方法:

	//ActivityThread.java
	//沒有前後文的H中的handleMessage~~~
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
			//ActivityClientRecord 是apk程式中一個Activity的代表,這個物件的activity成員引用真正的Activity元件,後面的都和它有關係
                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");///這裡~這裡~
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
複製程式碼
    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
            ...
            Activity a = performLaunchActivity(r, customIntent);
	    ...
    }

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
        if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config, //看到這個r.lastNonConfigurationInstances 就是在Activity方法中呼叫getLastNonConfigurationInstance()獲取到的Object了。
                        r.referrer, r.voiceInteractor, window, r.configCallback);
    ...
    }
複製程式碼

註釋中的地方就是lastNonConfigurationInstances的賦值的地方,可能會發現在scheduleLaunchActivity並沒有對lastNonConfigurationInstances賦值,因為第一次啟動Activity時,這裡其實就是null的,那麼賦值的地方在哪裡呢,既然是銷燬後會恢復資料,追蹤發現在performDestroyActivity()也就是在呼叫onDestroy生命週期之前有這樣一段程式碼

private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance) {
        ActivityClientRecord r = mActivities.get(token);
        ...無關程式碼省略
            if (getNonConfigInstance) {
                try {
                    r.lastNonConfigurationInstances
                            = r.activity.retainNonConfigurationInstances();///就是這裡出現了想要找的NonConfigurationInstances
                } catch (Exception e) {
                    if (!mInstrumentation.onException(r.activity, e)) {
                        throw new RuntimeException(
                                "Unable to retain activity "
                                + r.intent.getComponent().toShortString()
                                + ": " + e.toString(), e);
                    }
                }
            }
            try {
                r.activity.mCalled = false;
                mInstrumentation.callActivityOnDestroy(r.activity);
	...無關程式碼省略
        return r;
    }
複製程式碼

在performDestroyActivity()呼叫了Activity.retainNonConfigurationInstances()方法了,所以邏輯切換回Activity中...

     /**
     * This method is similar to {@link #onRetainNonConfigurationInstance()} except that
     * it should return either a mapping from  child activity id strings to arbitrary objects,
     * or null.  This method is intended to be used by Activity framework subclasses that control a
     * set of child activities, such as ActivityGroup.  The same guarantees and restrictions apply
     * as for {@link #onRetainNonConfigurationInstance()}.  The default implementation returns null.
     */
    @Nullable
    HashMap<String,Object> onRetainNonConfigurationChildInstances() {
        return null;
    }

    NonConfigurationInstances retainNonConfigurationInstances() {
        Object activity = onRetainNonConfigurationInstance();///熟悉的程式碼,原來的配方,和分析ActivityThread之前聯絡起來了,在Activity中是空實現,這裡就是獲取子類的NonConfigurationInstance(),之前的例子就是的得FragmentActivity中的具體實現,上文中已經在分析ActivityThread.java已經指出。
        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        // We're already stopped but we've been asked to retain.
        // Our fragments are taken care of but we need to mark the loaders for retention.
        // In order to do this correctly we need to restart the loaders first before
        // handing them off to the next activity.
        mFragments.doLoaderStart();
        mFragments.doLoaderStop(true);
        ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

        if (activity == null && children == null && fragments == null && loaders == null
                && mVoiceInteractor == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        if (mVoiceInteractor != null) {
            mVoiceInteractor.retainInstance();
            nci.voiceInteractor = mVoiceInteractor;
        }
        return nci;//這裡返回的是Activity中的NonConfigurationInstances就儲存在了ActivityClientRecord中了
    }
複製程式碼

至此,ActivityClientRecord就不再深入了,可以看到在Activity中是以一個ArrayMap來儲存Activity的記錄,記錄的就是Activity的狀態,所以這裡就實現了對NonConfigurationInstances的儲存。


結語:至此就基本看完了ViewModel在Activity中的使用和原理,在Fragment中的實現主要是使用setRetainInstance(true)的方式去儲存,跟今天的分析也有關聯,分析原始碼的過程總是看著就有新的問題,再次帶著問題去解決會再次有不同的收穫,本文的理解也可能有偏差,如有錯誤和想要交流的也歡迎指正溝通。

相關文章