知乎 Matisse 源码分析

首先贴上项目 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