Android之Loader介绍

本文介绍Loader, CursorLoader, AsyncTaskLoader 和 LoaderManager。先给出它们的使用示例,然后再对它们的架构和原理进行分析。

目录
1. Loader简介
2. Loader使用示例
3. Loader架构和原理

Loader简介

Loader:是一个执行异步数据加载的抽象类。它是AsyncTaskLoader的父类,CursorLoader的祖父类。

AsyncTaskLoader:它是继承于Loader的抽象类。在AsyncTaskLoader中有AsyncTask用来执行异步工作。

CursorLoader:它是AsyncTaskLoader的子类。它可以查询ContentResolver然后返回一个Cursor,同时CursorLoader内包含ContentObserver对象来监听Cursor数据的变化。

LoaderManager:它是一个抽像类,作用是用来管理Loader。LoaderManager关联到一个Activity或Fragment;例如,在Activity中通过getLoaderManager()可以获取到一个LoaderManager对象。

LoaderManager.LoaderCallbacks:它提供了与"Activity或Fragment"交互的接口。LoaderCallbacks中共包行三个接口:

  1. onCreateLoader():根据所给出的ID,初始化并返回一个新的加载器。

  2. onLoadFinished():当一个先前被创建的加载器完成了它的加载过程时被调用。

  3. onLoaderReset():当一个先前被创建的加载器被重置时被调用,然后使加载器的数据无效。

总结:Loader, AsyncTaskLoader和CursorLoader都是用来提供异步加载的类;而LoaderManager则提供了管理这些异步加载类的接口。此外,Activity或Fragment保护了获取LoaderManager的接口,通过这样的接口,就可以在Activity或Fragment中实现异步加载任务!

Loader使用示例

下面通过一则示例演示Fragment中执行异步加载的完整流程!该示例中,会在FragmentActivity中采用异步加载的方式查找并显示数据库中所有的图片的相关信息。

点击查看:Loader示例的完整源码

1. 获取LoaderManager,并执行initLoader()

public class LoaderTest extends FragmentActivity
    implements LoaderCallbacks<Cursor>, OnItemClickListener {

    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        listView = (ListView)findViewById(android.R.id.list);
        simpleCursorAdapter = new SimpleCursorAdapter(
                this, 
                R.layout.list_item, 
                null, 
                STORE_IMAGES, 
                new int[] { R.id.item_title, R.id.item_value}
                );  

        simpleCursorAdapter.setViewBinder(new ImageLocationBinder());
        listView.setAdapter(simpleCursorAdapter);
        // 注意此处是getSupportLoaderManager(),而不是getLoaderManager()方法。
        getSupportLoaderManager().initLoader(0, null, this);

        listView.setOnItemClickListener(this);
    }   
}

说明:需要重点关注的是getSupportLoaderManager(),它的作用是获取LoaderManager。然后,通过initLoader()初始化Loader。

initLoader()的完整声明如下:

public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback);

说明:
(01) id是Loader的唯一标识。
(02) args是Bundle,这里赋值位null。
(03) callback是回调函数。后面会给出回调的实现。

2. 实现LoaderManager.LoaderCallbacks接口

LoaderCallbacks中包行onCreateLoader(), onLoaderReset(), onLoadFinished()三个接口。下面是它们的实现:

@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
    CursorLoader cursorLoader = new CursorLoader(
            this, 
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
            STORE_IMAGES, 
            null, 
            null, 
            null);
    return cursorLoader;
}

@Override
public void onLoaderReset(Loader<Cursor> arg0) {
    simpleCursorAdapter.swapCursor(null);
}

@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
    simpleCursorAdapter.swapCursor(cursor);
}

说明:
(01) onCreateLoader()会在初始化Loader之后执行,它的作用是返回一个Loader对象。
(02) 而onLoadFinished()是当异步加载完成之后会被执行,当Loader加载完数据时,我们会通过 simpleCursorAdapter.swapCursor(cursor)将simpleCursorAdapter切换到新的Cursor对象(即,加载完后保存数据的对象)。不同于changeCursor(),changeCursor()在切换到新的Cursor之后,会将旧的Cursor关闭掉;而swapCursor()切换到新的Cursor之后,不会关闭旧的Cursor,如果旧的Cursor存在的话,它会返回旧的Cursor。
(03) onLoaderReset()则是当异步加载起被重置时会被调用。当Loader被重置会,我们通过simpleCursorAdapter.swapCursor(null)将Cursor设为null。

3. 其他操作

经过上面两步之后,已经能成功的使用异步加载数据了。得到的数据保存在SimpleCursorAdapter对象中,接下来需要做的就是在SimpleCursorAdapter中显示数据。

在onCreate()中,我们通过simpleCursorAdapter.setViewBinder(new ImageLocationBinder())设置了SimpleCursorAdapter的ViewBinder对象。下面看看ViewBinder中是如何显示的:

// 将图片的位置绑定到视图
private class ImageLocationBinder implements ViewBinder {
    @Override
    public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
        if (columnIndex == 1) {
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            String path = cursor.getString(column_index);
            ((TextView)view).setText("Location: "+path);
            return true;
        }

        return false;
    }
}

至此,数据的异步加载示例差不多介绍完了。总的来说很简单,先获取LoaderManager对象,然后通过initLoader()初始化Loader。接着,需要作的是实现LoaderCallbacks接口!LoaderCallbacks接口中,需要重点关注的是onCreateLoader()和onLoadFinished()接口!

Loader架构和原理

在Loader简介中,我们大致介绍了Loader, LoaderManager各个类的作用。这里,我们分析一下它们的源码来查看它们是如何工作的。主要解决的有以下几个问题:
(01) Loader的异步加载原理。也就是说Loader异步是通过什么实现的?是一个Thread,还是AsyncTask,还是其他的什么方式
(02) onCreateLoader()和onLoadFinished()是如何回调的。尤其是onLoadFinished(),当数据加载完时,是如何回调的
(03) 当发生Activity或Fragment被重新加载(例如,发生旋屏动作)时,Loader不会重新加载,它里面的数据仍然存在。这是如何做到的
(04) Loader异步加载器,能够自动更新数据。它又是如何做到的

1. 异步加载原理

涉及到的文件:
frameworks/base/core/java/android/app/LoaderManager.java
frameworks/base/core/java/android/content/Loader.java
frameworks/base/core/java/android/content/AsyncTaskLoader.java
frameworks/base/core/java/android/content/CursorLoader.java

1.1 initLoader()

LoaderManager.java中initLoader()源码如下:

public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
    if (mCreatingLoader) {
        throw new IllegalStateException("Called while creating a loader");
    }   

    LoaderInfo info = mLoaders.get(id);

    if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);

    if (info == null) {
        // Loader doesn't already exist; create.
        info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        if (DEBUG) Log.v(TAG, "  Created new loader " + info);
    } else {
        if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
        info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
    }   

    if (info.mHaveData && mStarted) {
        // If the loader has already generated its data, report it now.
        info.callOnLoadFinished(info.mLoader, info.mData);
    }   

    return (Loader<D>)info.mLoader;
}   

说明:
(01) initLoader()会现通过mLoaders.get(id)来根据id获取LoaderInfo对象。如果Activity或Fragment第一次调用initLoader(),则获取到的LoaderInfo对象为null。
(02) 如果info为null,则执行createAndInstallLoader()来创建LoaderInfo对象。

1.2 createAndInstallLoader()

LoaderManager.java中createAndInstallLoader()源码如下:

private LoaderInfo createLoader(int id, Bundle args,
        LoaderManager.LoaderCallbacks<Object> callback) {
    LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
    Loader<Object> loader = callback.onCreateLoader(id, args);
    info.mLoader = (Loader<Object>)loader;
    return info;
}

private LoaderInfo createAndInstallLoader(int id, Bundle args,
        LoaderManager.LoaderCallbacks<Object> callback) {
    try {
        mCreatingLoader = true;
        LoaderInfo info = createLoader(id, args, callback);
        installLoader(info);
        return info;
    } finally {
        mCreatingLoader = false;
    }
}

void installLoader(LoaderInfo info) {
    mLoaders.put(info.mId, info);
    if (mStarted) {
        // The activity will start all existing loaders in it's onStart(),
        // so only start them here if we're past that point of the activitiy's
        // life cycle
        info.start();
    }
}

说明:
(01) createAndInstallLoader()会分别调用createLoader()和installLoader()接口。
(02) createLoader()会新建LoaderInfo对象,然后调用callback.onCreateLoader()接口。这里的onCreateLoader()就是我们实现的LoaderCallbacks接口中的onCreateLoader()接口
(03) initLoader()中会调用info.start()接口。

1.3 LoaderInfo中的start()

LoaderManager.java中的内部类LoaderInfo的源码如下:

final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,
        Loader.OnLoadCanceledListener<Object> {

    final int mId;
    final Bundle mArgs;
    LoaderManager.LoaderCallbacks<Object> mCallbacks;
    Loader<Object> mLoader;

    ...

    public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
        mId = id;
        mArgs = args;
        mCallbacks = callbacks;
    }

    void start() {
        if (mRetaining && mRetainingStarted) {
            mStarted = true;
            return;
        }

        if (mStarted) {
            return;
        }

        mStarted = true;

        if (DEBUG) Log.v(TAG, "  Starting: " + this);
        if (mLoader == null && mCallbacks != null) {
           mLoader = mCallbacks.onCreateLoader(mId, mArgs);
        }
        if (mLoader != null) {
            if (mLoader.getClass().isMemberClass()
                    && !Modifier.isStatic(mLoader.getClass().getModifiers())) {
                throw new IllegalArgumentException(
                        "Object returned from onCreateLoader must not be a non-static inner member class: "
                        + mLoader);
            }
            if (!mListenerRegistered) {
                mLoader.registerListener(mId, this);
                mLoader.registerOnLoadCanceledListener(this);
                mListenerRegistered = true;
            }
            mLoader.startLoading();
        }
    }

    ...
}

说明:start()中一个非常重要的成员是mLoader。它就是onCreateLoader()返回的Loader对象。start()会调用mLoaders.startLoading()。

1.4 startLoading()

下面查看startLoading的相关代码。查看代码时,请记得:Loader.java, AsyncTaskLoader和CursorLoader这三者之间存在继承关系!

// 在Loader.java中
public final void startLoading() {
    mStarted = true;
    mReset = false;
    mAbandoned = false;
    onStartLoading();
}

// 在CursorLoader.java中
@Override
protected void onStartLoading() {
    if (mCursor != null) {
        deliverResult(mCursor);
    }   
    if (takeContentChanged() || mCursor == null) {
        forceLoad();
    }   
}   

说明:startLoading()是在Loader.java中定义的,它调用了onStartLoading()。而CursorLoader.java覆盖了onStartLoading()函数。在onStartLoading()中,此时的mCursor是为null;所以,它会执行forceLoad()。

1.5 forceLoad()

下面查看forceLoad的相关代码。

// 在Loader.java中
public void forceLoad() {
    onForceLoad();
}

// 在AsyncTaskLoader.java中
@Override
protected void onForceLoad() {
    super.onForceLoad();
    cancelLoad();
    mTask = new LoadTask();
    if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
    executePendingTask();
}  

// 在AsyncTaskLoader.java中
void executePendingTask() {
    if (mCancellingTask == null && mTask != null) {
        if (mTask.waiting) {
            mTask.waiting = false;
            mHandler.removeCallbacks(mTask);
        }
        if (mUpdateThrottle > 0) {
            long now = SystemClock.uptimeMillis();
            if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
                // Not yet time to do another load.
                if (DEBUG) Slog.v(TAG, "Waiting until "
                        + (mLastLoadCompleteTime+mUpdateThrottle)
                        + " to execute: " + mTask);
                mTask.waiting = true;
                mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
                return;
            }
        }
        if (DEBUG) Slog.v(TAG, "Executing: " + mTask);
        mTask.executeOnExecutor(mExecutor, (Void[]) null);
    }
}

说明:forceLoad()会调用onForceLoad()。而onForceLoad()中会新建LoadTask对象,然后执行executePendingTask()。在executePendingTask()中会调用LoadTask对象的executeOnExecutor()。

1.6 LoadTask

LoadTask是AsyncTaskLoader的内部类。实际上,它是AsyncTask的子类。在介绍AsyncTask的文章中,我们知道executeOnExecutor()会将任务提交到线程池中去执行,而这个被提交到线程池的任务会执行AsyncTask的doInBackground()。

// AsyncTaskLoader.java中
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
    private final CountDownLatch mDone = new CountDownLatch(1);

    boolean waiting;

    @Override
    protected D doInBackground(Void... params) {
        if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
        try {
            D data = AsyncTaskLoader.this.onLoadInBackground();
            if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground");
            return data;
        } catch (OperationCanceledException ex) {
            if (!isCancelled()) {
                throw ex;
            }
            if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
            return null;
        }
    }

    /* Runs on the UI thread */
    @Override
    protected void onPostExecute(D data) {
        if (DEBUG) Slog.v(TAG, this + " onPostExecute");
        try {
            AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
        } finally {
            mDone.countDown();
        }
    }

    ...
}

说明:LoadTask的doInBackground()会调用onLoadInBackground()。

1.7 onLoadInBackground()

下面查看onLoadInBackground()的相关代码。

// AsyncTaskLoader.java中
protected D onLoadInBackground() {
    return loadInBackground();
}

// CursorLoader.java中
@Override
public Cursor loadInBackground() {
    synchronized (this) {
        if (isLoadInBackgroundCanceled()) {
            throw new OperationCanceledException();
        }
        mCancellationSignal = new CancellationSignal();
    }           
    try {
        Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                mSelectionArgs, mSortOrder, mCancellationSignal);
        if (cursor != null) {
            try {
                // Ensure the cursor window is filled.
                cursor.getCount();
                cursor.registerContentObserver(mObserver);
            } catch (RuntimeException ex) {
                cursor.close();
                throw ex;
            } 
        }
        return cursor;
    } finally {
        synchronized (this) {
            mCancellationSignal = null;
        }
    }
}   

说明:loadInBackground()会执行查询。这就是后台任务真正执行的动作所在!

至此,我们已经回答了第一个问题:Loader的异步加载原理。实际上,Loader的异步加载是通过AsyncTask启动一个后台任务,在后台任务中再执行query()查询操作!

2. onLoadFinished是如何被回调的

我们知道,当AsyncTask中的任务执行完时,会通过onPostExecute()反馈执行结果。下面,我们看看AsyncTask反馈的结果中是如何调用onLoadFinished()的。

2.1 onPostExecute

下面看看onPostExecute()的相关代码。

// AsyncTaskLoader.java中
@Override
protected void onPostExecute(D data) {
    if (DEBUG) Slog.v(TAG, this + " onPostExecute");
    try {
        AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
    } finally {
        mDone.countDown();
    }
}

// AsyncTaskLoader.java中
void dispatchOnLoadComplete(LoadTask task, D data) {
    if (mTask != task) {
        if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
        dispatchOnCancelled(task, data);
    } else {
        if (isAbandoned()) {
            // This cursor has been abandoned; just cancel the new data.
            onCanceled(data);
        } else {
            commitContentChanged();
            mLastLoadCompleteTime = SystemClock.uptimeMillis();
            mTask = null;
            if (DEBUG) Slog.v(TAG, "Delivering result");
            deliverResult(data);
        }
    }
}

说明:onPostExecute()会执行dispatchOnLoadComplete(),而后者会调用deliverResult()来分发消息。

2.2 deliverResult()

// CursorLoader.java中
@Override
public void deliverResult(Cursor cursor) {
    if (isReset()) {
        // An async query came in while the loader is stopped
        if (cursor != null) {
            cursor.close();
        }   
        return;
    }   
    Cursor oldCursor = mCursor;
    mCursor = cursor;

    if (isStarted()) {
        super.deliverResult(cursor);
    }   

    if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
        oldCursor.close();
    }   
}   

// Loader.java中
public void deliverResult(D data) {
    if (mListener != null) {
        mListener.onLoadComplete(this, data);
    }
}

说明:CursorLoader.java中的deliverResult()会通过super.deliverResult()调用父类的deliverResult(),也就是会执行Loader.java中的deliverResult()。最终会执行mListener.onLoadComplete()。mListener是什么呢?它是我们在执行LoaderManager.java的start()函数时,通过mLoader.registerListener(mId, this)注册到Loader上的。也就是说,mListener是LoaderManager中的LoaderInfo对象。

2.3 LoaderInfo中的onLoadComplete()

@Override
public void onLoadComplete(Loader<Object> loader, Object data) {
    if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);

    if (mDestroyed) {
        if (DEBUG) Log.v(TAG, "  Ignoring load complete -- destroyed");
        return;
    }

    if (mLoaders.get(mId) != this) {
        if (DEBUG) Log.v(TAG, "  Ignoring load complete -- not active");
        return;
    }

    LoaderInfo pending = mPendingLoader;
    if (pending != null) {
        mPendingLoader = null;
        mLoaders.put(mId, null);
        destroy();
        installLoader(pending);
        return;
    }

    if (mData != data || !mHaveData) {
        mData = data;
        mHaveData = true;
        if (mStarted) {
            callOnLoadFinished(loader, data);
        }
    }

    LoaderInfo info = mInactiveLoaders.get(mId);
    if (info != null && info != this) {
        info.mDeliveredData = false;
        info.destroy();
        mInactiveLoaders.remove(mId);
    }

    if (mActivity != null && !hasRunningLoaders()) {
        mActivity.mFragments.startPendingDeferredFragments();
    }
}

void callOnLoadFinished(Loader<Object> loader, Object data) {
    if (mCallbacks != null) {
        String lastBecause = null;
        if (mActivity != null) {
            lastBecause = mActivity.mFragments.mNoTransactionsBecause;
            mActivity.mFragments.mNoTransactionsBecause = "onLoadFinished";
        }
        try {
            if (DEBUG) Log.v(TAG, "  onLoadFinished in " + loader + ": "
                    + loader.dataToString(data));
            mCallbacks.onLoadFinished(loader, data);
        } finally {
            if (mActivity != null) {
                mActivity.mFragments.mNoTransactionsBecause = lastBecause;
            }
        }
        mDeliveredData = true;
    }
}

说明:onLoadComplete()会调用callOnLoadFinished(),而callOnLoadFinished()最终会调用onLoadFinished()。这就是我们实现的LoaderCallbacks接口中的onLoadFinished()

至此,我们也搞明白了第二个问题onLoadFinished()是如何被回调的。当AsyncTask任务执行完时,会调用onPostExecute();而onPostExecute()会通过LoaderManager,LoaderManager最终会调用onLoadFinished()。

3. 当Activity或Fragment被重新加载时,Loader不会被重新加载。

这个原理其实非常简单,关键就是initLoader()的实现方式。

public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
    if (mCreatingLoader) {
        throw new IllegalStateException("Called while creating a loader");
    }   

    LoaderInfo info = mLoaders.get(id);

    if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);

    if (info == null) {
        // Loader doesn't already exist; create.
        info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        if (DEBUG) Log.v(TAG, "  Created new loader " + info);
    } else {
        if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
        info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
    }   

    if (info.mHaveData && mStarted) {
        // If the loader has already generated its data, report it now.
        info.callOnLoadFinished(info.mLoader, info.mData);
    }   

    return (Loader<D>)info.mLoader;
}   

说明:initLoader()会首先调用mLoaders.get(id)来获取id对应的LoaderInfo对象。如果Activity或Fragment是由于旋屏而发生重载,那么通过id获取到的LoaderInfo就不是null。那么initLoader()就会调用info.callOnLoadFinished()。

4. Loader异步加载器自动更新数据

Loader自动更新数据是通过ContentObserver来实现的。

4.1 注册ContentResolver

// CursorLoader.java中
@Override
public Cursor loadInBackground() {
    synchronized (this) {
        if (isLoadInBackgroundCanceled()) {
            throw new OperationCanceledException();
        }
        mCancellationSignal = new CancellationSignal();
    }           
    try {
        Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                mSelectionArgs, mSortOrder, mCancellationSignal);
        if (cursor != null) {
            try {
                // Ensure the cursor window is filled.
                cursor.getCount();
                cursor.registerContentObserver(mObserver);
            } catch (RuntimeException ex) {
                cursor.close();
                throw ex;
            } 
        }
        return cursor;
    } finally {
        synchronized (this) {
            mCancellationSignal = null;
        }
    }
}   

说明:在执行query()查询之后,如果得到的Cursor对象不是null。则通过cursor.registerContentObserver(mObserver)来监听该cursor的内容。

4.2 注册mObserver

// CursorLoader.java中
public CursorLoader(Context context) {
    super(context);
    mObserver = new ForceLoadContentObserver();
}

// CursorLoader.java中
public CursorLoader(Context context, Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {
    super(context);
    mObserver = new ForceLoadContentObserver();
    mUri = uri;
    mProjection = projection;
    mSelection = selection;
    mSelectionArgs = selectionArgs;
    mSortOrder = sortOrder;
}

说明:在创建CursorLoader对象时,就创建了mObserver,它是ForceLoadContentObserver对象。

4.3 ForceLoadContentObserver

// Loader.java
public final class ForceLoadContentObserver extends ContentObserver {
    public ForceLoadContentObserver() {
        super(new Handler());
    }

    @Override
    public boolean deliverSelfNotifications() {
        return true;
    }

    @Override
    public void onChange(boolean selfChange) {
        onContentChanged();
    }
}

// Loader.java中
public void onContentChanged() {
    if (mStarted) {
        forceLoad();
    } else {
        // This loader has been stopped, so we don't want to load
        // new data right now...  but keep track of it changing to
        // refresh later if we start again.
        mContentChanged = true;
    }
}

说明:ForceLoadContentObserver是Loader.java的内部类,它继承于ContentObserver。当数据发生变化时,它会回调onChange(),进而调用onContentChanged()。而onContentChanged()则会执行forceLoad()。forceLoad()在前面已经介绍过了,它会启动AsyncTask被执行query查询操作!

至此,几个问题都回答完了。Loader, AsyncTaskLoader, CursorLoader和LoaderManager的相互关系也整理完了!谢谢观赏!

by skywang
Previous     Next