Android 7.0 Gallery图库源码分析3 - 数据加载及显示流程

转载请注明出处: http://blog.csdn.net/lb377463323/article/details/69569133

前面分析Gallery启动流程时,说了传给DataManager的data的key是AlbumSetPage.KEY_MEDIA_PATH,value值,是”/combo/{/local/all,/picasa/all}”,下面分析具体怎么加载数据的。

数据加载的准备阶段

数据初始化是在AlbumSetPage的initializeData方法中。

private void initializeData(Bundle data) {
        //mediaPath即为"/combo/{/local/all,/picasa/all}"
        String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH);
        //获取MediaSet来管理一组媒体数据
        mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
        /mSelectionManager用于管理选择事件
        mSelectionManager.setSourceMediaSet(mMediaSet);
        //mAlbumSetDataAdapter类似于桥梁来连接页面和数据源
        mAlbumSetDataAdapter = new AlbumSetDataLoader(
                mActivity, mMediaSet, DATA_CACHE_SIZE);
        //设置数据加载的监听接口
        mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener());
        mAlbumSetView.setModel(mAlbumSetDataAdapter);
    }

mActivity.getDataManager()就是获取Application(GalleryAppImpl)的DataManager,我们接着看getMediaSet方法,

//根据路径获取MediaObject,s为"/combo/{/local/all,/picasa/all}"
public MediaSet getMediaSet(String s) {
        return (MediaSet) getMediaObject(s);
    }

public MediaObject getMediaObject(String s) {
        return getMediaObject(Path.fromString(s));
    }

//进到PATH类中
private WeakReference<MediaObject> mObject;
private IdentityCache<String, Path> mChildren;

public static Path fromString(String s) {
        synchronized (Path.class) {
            String[] segments = split(s);
            //segments为["combo", "{/local/all,/picasa/all}"]
            Path current = sRoot;
            for (int i = 0; i < segments.length; i++) {
                current = current.getChild(segments[i]);
            }
            //经过for循环,current会持有两条路径,"combo"为父PATH,"{/local/all,/picasa/all}"为子PATH
            return current;
        }
    }

//获取PATH对应得MediaObject
public MediaObject getMediaObject(Path path) {
        synchronized (LOCK) {
            //根据PATH获取MediaObject,不为空直接返回
            MediaObject obj = path.getObject();
            if (obj != null) return obj;

            //根据PATH的前缀获取mSourceMap对应的MediaSource,mSourceMap初始化在源码分析2中讲过,这里返回的就是ComboSource
            MediaSource source = mSourceMap.get(path.getPrefix());
            ......

            try {
                //走到这里说明MediaObject为空,所以需要创建MediaObject
                MediaObject object = source.createMediaObject(path);
                return object;
            ......
        }
    }

我们接着看下ComboSource的createMediaObject方法

public MediaObject createMediaObject(Path path) {
        //segments为["combo", "{/local/all,/picasa/all}"]
        String[] segments = path.split();
        ......
        //match结果为COMBO_ALBUMSET
        switch (mMatcher.match(path)) {
            //创建一个ComboAlbumSet并返回,dataManager.getMediaSetsFromString(segments[1])这个方法就是根据"{/local/all,/picasa/all}"创建LocalSource实例和PicasaSource实例以及对应的LocalAlbumSet实例和EmptyAlbumSet实例,这个过程就是重复上述步骤
            case COMBO_ALBUMSET:
                return new ComboAlbumSet(path, mApplication,
                        dataManager.getMediaSetsFromString(segments[1]));
        ......
    }

创建好后,最终返回给AlbumSetPage的initializeData方法中的mMediaSet

private void initializeData(Bundle data) { ...... //mMediaSet就是ComboAlbumSet,也就是数据源,它管理着LocalAlbumSetEmptyAlbumSet mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath); mSelectionManager.setSourceMediaSet(mMediaSet); //mAlbumSetDataAdapter类似于桥梁来连接页面和数据源 mAlbumSetDataAdapter = new AlbumSetDataLoader( mActivity, mMediaSet, DATA_CACHE_SIZE); mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener()); 将mAlbumSetDataAdapter传给界面显示的渲染器 mAlbumSetView.setModel(mAlbumSetDataAdapter); }

setModel这个方法挺重要的,它在AlbumSetSlotRenderer中,我们具体看一下

    public void setModel(AlbumSetDataLoader model) {
        ......
        if (model != null) {
            //根据model创建AlbumSetSlidingWindow,它是负责滑动显示图片的,比如解码专辑缩略图等
            mDataWindow = new AlbumSetSlidingWindow(
                    mActivity, model, mLabelSpec, CACHE_SIZE);
            //设置监听接口,处理尺寸改变或内容改变的事件
            mDataWindow.setListener(new MyCacheListener());
            mSlotView.setSlotCount(mDataWindow.size());
        }
    }

到这里数据源和数据源适配器都创建好了,并且也传给了AlbumSetPage页面,这样数据加载的准备工作就做好了,也就是onCreate方法执行结束,下面分析onResume方法,这里完成数据的实际加载过程。

数据加载过程

首先查看GalleryActivity的OnResume方法,

protected void onResume() {
        //调用其父类的OnResume方法
        super.onResume();
        }
    }

我们接着查看AbstractGalleryActivity的的OnResume方法

protected void onResume() {
        ......
        try {
            //数据加载的核心在这里
            getStateManager().resume(); 
            //这个方法只有LocalSource获取ContentProvider,别的都是什么操作都没有
            getDataManager().resume();
        } 
        mGLRootView.onResume();
        mOrientationManager.resume();
    }

StateManager().resume的方法如下:

    public void resume() {
        //我们是从桌面图标进的应用,所以getTopState获取的是AlbumSetPage
        if (!mStack.isEmpty()) getTopState().resume();
    }

我们看一下AlbumSetPage的resume方法,AlbumSetPage没有重写resume方法,所以调用的是其父类ActivityState的resume方法,我们先看一下

void resume() {
        ......
        //这里就是调用AlbumSetPage的onResume方法
        onResume();
        ......
    }

    public void onResume() {
        ......
        //数据加载就是这一步完成的
        mAlbumSetDataAdapter.resume();
        ......

前面讲了mAlbumSetDataAdapter是一个AlbumSetDataLoader类,所以我们去看AlbumSetDataLoader的resume方法

    public void resume() {
        //这个接口是数据变化的监听接口,当完成数据加载时会回调mSourceListener的onContentDirty方法
        mSource.addContentListener(mSourceListener);
        //ReloadTask就是完成数据加载任务的子线程
        mReloadTask = new ReloadTask();
        mReloadTask.start();
    }

我们看一下ReloadTask的run方法

public void run() {
    ......
    //这里执行数据加载
    long version = mSource.reload();
    ......
}

mSource是new AlbumSetDataLoader(
                mActivity, mMediaSet, DATA_CACHE_SIZE)传入的mMediaSet,前面讲了mMediaSet就是ComboAlbumSet,它包含一个LocalAlbumSet和EmptyAlbumSet

我们去ComboAlbumSet类中查看它的reload方法

    public long reload() {
        //mSets即为ComboAlbumSet所包含的LocalAlbumSet和EmptyAlbumSet,这里也就是分别调用LocalAlbumSet和EmptyAlbumSet的reload方法
        for (int i = 0, n = mSets.length; i < n; ++i) {
            long version = mSets[i].reload();
    ......

因为EmptyAlbumSet的reload方法就是返回数据版本,所以暂且不管它。下面只分析LocalAlbumSet的reload方法。

public synchronized long reload() {
        ......
        //通过ThreadPool线程池执行专辑数据的加载,AlbumsLoader方法看下面讲述
        mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this);
        //这里就是对每个专辑进行数据加载,这之后的就不讲了
        for (MediaSet album : mAlbums) {
                album.reload();
        }

submit方法就是把job和listener封装成一个Worker,然后传给ThreadPoolExecutor执行

public <T> Future<T> submit(Job<T> job, FutureListener<T> listener) {
        Worker<T> w = new Worker<T>(job, listener);
        mExecutor.execute(w);
        return w;
    }

ThreadPoolExecutor的execute方法最终也是执行Worker的run方法,现在看下Worker的run方法

public void run() {
    ......
    //mJob就是submit传进来的new AlbumsLoader() 
    result = mJob.run(this);
    ......
    //mListener是FutureListener接口,这里也就是LocalAlbumSet自身
    if (mListener != null) mListener.onFutureDone(this);
}

接着看下AlbumsLoader的run方法,这里主要是获取专辑的信息

private class AlbumsLoader implements ThreadPool.Job<ArrayList<MediaSet>> {

        @Override
        @SuppressWarnings("unchecked")
        public ArrayList<MediaSet> run(JobContext jc) {
            ......
            //通过BucketHelper获取所有的专辑信息
            BucketEntry[] entries = BucketHelper.loadBucketEntries(
                    jc, mApplication.getContentResolver(), mType);
        ......
        for (BucketEntry entry : entries) {
                //获取LocalAlbum并保存到ArrayList(albums)中,albums对应于每个专辑
                MediaSet album = getLocalAlbum(dataManager,
                        mType, mPath, entry.bucketId, entry.bucketName);
                albums.add(album);
            }

当AlbumsLoader的run方法执行完后,接着执行mListener.onFutureDone回调接口,通过这个接口通知MediaSet内容有变化。最终会走到AlbumSetDataLoader的onContentDirty方法

    public void onContentDirty() {
            //这个方法会唤醒所以wait的线程
            mReloadTask.notifyDirty();
        }

现在我们回到AlbumSetDataLoader的ReloadTask中,reload方法执行之后会通过updateLoading发送MSG_LOAD_FINISH消息

while (mActive) {
    ......
    //这一块很重要,用来更新界面的
    //获取需要更新的数据信息,包括专辑数量等,这里我不细讲了,自己看代码
    UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
    ......
    //根据数据信息更新界面,这个方法最终会执行UpdateContent的call方法
    executeAndWait(new UpdateContent(info));
}

//这个方法发送 MSG_LOAD_FINISH消息通知数据加载完成,这里不细讲了
updateLoading(false);

更新界面

private class UpdateContent implements Callable<Void> {
    public Void call() {
        //这里是更新Slot数目
        if (mDataListener != null) mDataListener.onSizeChanged(mSize);
        ......
        //更新内容
        mDataListener.onContentChanged(info.index);
    }
}

mDataListener是实例化AlbumSetSlidingWindow是设置的,也就是AlbumSetSlidingWindow自身

source.setModelListener(this);

接着看AlbumSetSlidingWindow的onSizeChanged和onContentChanged方法

    public void onSizeChanged(int size) {
        if (mIsActive && mSize != size) {
            mSize = size;
            //mListener是AlbumSetSlotRenderer的,MyCacheListener,onSizeChanged就是执行mSlotView.setSlotCount(size)
            if (mListener != null) mListener.onSizeChanged(mSize);
            if (mContentEnd > mSize) mContentEnd = mSize;
            if (mActiveEnd > mSize) mActiveEnd = mSize;
        }
    }

    public void onContentChanged(int index) {
        //更新图像
        AlbumSetEntry entry = mData[index % mData.length];
        updateAlbumSetEntry(entry, index);
        updateAllImageRequests();
        updateTextureUploadQueue();
        //onContentChanged方法就是执行mSlotView.invalidate()刷新界面
        if (mListener != null && isActiveSlot(index)) {
            mListener.onContentChanged();
        }
    }

到这里就完成了SlotView的渲染准备工作,至于怎么渲染到屏幕上见Gallery图库源码分析6

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/lb377463323/article/details/69569133
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞