Android开发androidAndroid开发经验谈

知乎 Matisse 源码分析

2017-08-06  本文已影响141人  hexiaosa

首先贴上项目 github 地址:https://github.com/zhihu/Matisse,一句话介绍就是一个 Android 图片和视频选择器。

这篇文章主要是从源码分析整体的设计和实现流程。
其使用代码:

Matisse.from(MainActivity.this)
        .choose(MimeType.allOf())
        .countable(true)
        .maxSelectable(9)
        .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
        .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
        .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
        .thumbnailScale(0.85f)
        .imageEngine(new GlideEngine())
        .forResult(REQUEST_CODE_CHOOSE);

整体可以分成两大模块来看,最后一句 forResult() 是一部分,前面是一部分。

前面部分的分析

这里面主要涉及到 Matisse, SelectionCreator, SelectionSpec 这三个类,Matisse.from(MainActivity.this)返回了 Matisse 对象,choose() 创建了 SelectionCreator 对象并返回。在创建 SelectionCreator 对象时,创建了一个 SelectorSepc 成员变量,并给其设置相关的参数。再往下面的代码都是调用 SelectionCreator 的方法,里面的实现基本都是设置参数操作( 通过修改SelectionSpec 成员变量),然后返回 SelectionCreator 对象。整个用代码来展示会更清晰:

Matisse 部分代码:

public final class Matisse {
    public static Matisse from(Activity activity) {
        return new Matisse(activity);
    }
    public static Matisse from(Fragment fragment) {
        return new Matisse(fragment);
    }
    public SelectionCreator choose(Set<MimeType> mimeTypes) {
        return this.choose(mimeTypes, true);
    }
    public SelectionCreator choose(Set<MimeType> mimeTypes, boolean mediaTypeExclusive) {
        return new SelectionCreator(this, mimeTypes, mediaTypeExclusive);
    }
} 

SelectionCreator 部分代码:

public final class SelectionCreator {

    private final Matisse mMatisse;
    private final SelectionSpec mSelectionSpec;
    ...
    SelectionCreator(Matisse matisse, @NonNull Set<MimeType> mimeTypes, boolean mediaTypeExclusive) {
        mMatisse = matisse;
        //这里创建了 SelectionSpec 变量,用于保存一些配置
        mSelectionSpec = SelectionSpec.getCleanInstance();
        mSelectionSpec.mimeTypeSet = mimeTypes;
        mSelectionSpec.mediaTypeExclusive = mediaTypeExclusive;
        mSelectionSpec.orientation = SCREEN_ORIENTATION_UNSPECIFIED;
    }
    public SelectionCreator countable(boolean countable) {
        mSelectionSpec.countable = countable;
        return this;
    }
    public SelectionCreator maxSelectable(int maxSelectable) {
        if (maxSelectable < 1)
            throw new IllegalArgumentException("maxSelectable must be greater than or equal to one");
        mSelectionSpec.maxSelectable = maxSelectable;
        return this;
    }
    //其余方法都类似上面这两个,这里面就不贴出来了
}

SelectionSpec 部分代码:

public final class SelectionSpec {

    public Set<MimeType> mimeTypeSet;
    public boolean mediaTypeExclusive;
    public boolean showSingleMediaType;
    @StyleRes
    public int themeId;
    public int orientation;
    public boolean countable;
    public int maxSelectable;
    public List<Filter> filters;
    public boolean capture;
    public CaptureStrategy captureStrategy;
    public int spanCount;
    public int gridExpectedSize;
    public float thumbnailScale;
    public ImageEngine imageEngine;

    public static SelectionSpec getCleanInstance() {
        SelectionSpec selectionSpec = getInstance();
        selectionSpec.reset();
        return selectionSpec;
    }
}

第一部分的分析到底结束。


后面部分分析

public void forResult(int requestCode) {
    Activity activity = mMatisse.getActivity();//即为创建 Matisse 对象时传入的
    if (activity == null) {
        return;
    }

    Intent intent = new Intent(activity, MatisseActivity.class);

    Fragment fragment = mMatisse.getFragment();
    if (fragment != null) {
        fragment.startActivityForResult(intent, requestCode);
    } else {
        activity.startActivityForResult(intent, requestCode);
    }
}

从你自己的 Activity startActivityForResult(),所以在 onActivityResult 中拿到选中的图片 uri 或 path。如下代码:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_CHOOSE && resultCode == RESULT_OK) {
        mAdapter.setData(Matisse.obtainResult(data), Matisse.obtainPathResult(data));
    }
}

启动的 Activity 就是我们看到的展示图片的 MatisseActivity.
在 MatisseActivity 的 onCreate() 方法中第一句就看到了
mSpec = SelectionSpec.getInstance();

public static SelectionSpec getInstance() {
    return InstanceHolder.INSTANCE;
}
private static final class InstanceHolder {
    private static final SelectionSpec INSTANCE = new SelectionSpec();
}

由于 INSTANCE 是静态的,所以获取的和之前修改变量值的是同一个对象,这样就把之前设置的值都用在这里了。

先来聊一下获取资源以及展示
public class MatisseActivity extends AppCompatActivity implements
        AlbumCollection.AlbumCallbacks, ... {
    ...
    //用于保存资源以及资源操作
    private final AlbumCollection mAlbumCollection = new AlbumCollection();
    //用于展示资源的 Adapter
    private AlbumsAdapter mAlbumsAdapter;
    ...
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        //获取资源的主要代码
        mAlbumCollection.onCreate(this, this);
        mAlbumCollection.onRestoreInstanceState(savedInstanceState);
        mAlbumCollection.loadAlbums();
    }
    //拿到资源后回调方法
    @Override
    public void onAlbumLoad(final Cursor cursor) {
        mAlbumsAdapter.swapCursor(cursor);
        ...
}

mAlbumCollection.onCreate(this, this); 绑定了 Activity, AlbumCallbacks, 创建了 mLoaderManager (LoaderManagerImpl类型的).
mAlbumCollection.loadAlbums(); 调用了mLoaderManager.initLoader(LOADER_ID, null, this);

@Override
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
    ...
    LoaderInfo info = mLoaders.get(id);
    if (info == null) {
        info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
    }
    ...
    if (info.mHaveData && mStarted) {
        // 创建并获取资源完成后调用该方法,执行到AlbumCollection 中重写的 onLoadingFinish() 方法,里面又 callbacks.onAlbumLoad()
        info.callOnLoadFinished(info.mLoader, info.mData);
    }
}

createAndInstallLoader 执行到下面这里

private LoaderInfo createAndInstallLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
    try {
        mCreatingLoader = true;
        //在 AlbumCollection 中重写了该方法,创建了指定好 query 语句的 AlbumLoader 对象
        LoaderInfo info = createLoader(id, args, callback);
        //调用 info.start(), 在 CursorLoader 中实现 onStartLoading()
        installLoader(info);
        return info;
    } finally {
        mCreatingLoader = false;
}
再来聊一下选中资源
public class MatisseActivity extends AppCompatActivity ... {
    //用于保存所选中的资源
    private final AlbumCollection mAlbumCollection = new AlbumCollection();
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        //初始化 SelectedCollection 内部
        mSelectedCollection.onCreate(savedInstanceState);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.button_preview) {
            ...
        } else if (v.getId() == R.id.button_apply) {
            Intent result = new Intent();
            //返回 List<Uri>
            ArrayList<Uri> selectedUris = (ArrayList<Uri>) mSelectedCollection.asListOfUri();
            result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selectedUris);
            //返回 List<String>
            ArrayList<String> selectedPaths = (ArrayList<String>) mSelectedCollection.asListOfString();
            result.putStringArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPaths);
            setResult(RESULT_OK, result);
            finish();
        }
    }

    @Override
    public void onMediaClick(Album album, Item item, int adapterPosition) {
        Intent intent = new Intent(this, AlbumPreviewActivity.class);
        //在 BasePreviewActivity 中,点击事件来操作 mSelectedCollection add 还是 remove
        intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle());
        //把 Uri 和 Path 返回给你的 Activity
        startActivityForResult(intent, REQUEST_CODE_PREVIEW);
    }
}

下面来看一下 SeletedItemCollection :

public class SelectedItemCollection {
    //保存选中的 items
    private Set<Item> mItems;

    //初始化,可以看出其实内部是用 LinkedHashSet 保存 items 的
    public void onCreate(Bundle bundle) {
        if (bundle == null) {
            mItems = new LinkedHashSet<>();
        } else {
            List<Item> saved = bundle.getParcelableArrayList(STATE_SELECTION);
            mItems = new LinkedHashSet<>(saved);
            mCollectionType = bundle.getInt(STATE_COLLECTION_TYPE, COLLECTION_UNDEFINED);
        }
    }
}

总结

主要的类:
Matisse : 入口
SelectionSpec : 全程保存参数配置的类
SelectionCreator : 类似创建和配置的媒介
MatisseActivity : 主要的展示界面
AlbumCollection : 展示资源的操作,包括创建 AlbumLoader 和 回调给 MatisseActivity
SelectedItemCollection : 用 LinkedHashSet 来保存选中 items

上一篇 下一篇

猜你喜欢

热点阅读