zhihu/Matisse
:fireworks: A well-designed local image and video selector for Android
Stale — last commit 3y ago
weakest axislast commit was 3y ago; no tests detected
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓18 active contributors
- ✓Apache-2.0 licensed
- ✓CI configured
Show all 6 evidence items →Show less
- ⚠Stale — last commit 3y ago
- ⚠Concentrated ownership — top contributor handles 66% of recent commits
- ⚠No test directory detected
What would change the summary?
- →Use as dependency Mixed → Healthy if: 1 commit in the last 365 days
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Forkable" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/zhihu/matisse)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/zhihu/matisse on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: zhihu/Matisse
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/zhihu/Matisse shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
WAIT — Stale — last commit 3y ago
- 18 active contributors
- Apache-2.0 licensed
- CI configured
- ⚠ Stale — last commit 3y ago
- ⚠ Concentrated ownership — top contributor handles 66% of recent commits
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live zhihu/Matisse
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/zhihu/Matisse.
What it runs against: a local clone of zhihu/Matisse — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in zhihu/Matisse | Confirms the artifact applies here, not a fork |
| 2 | License is still Apache-2.0 | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 1119 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of zhihu/Matisse. If you don't
# have one yet, run these first:
#
# git clone https://github.com/zhihu/Matisse.git
# cd Matisse
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of zhihu/Matisse and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "zhihu/Matisse(\\.git)?\\b" \\
&& ok "origin remote is zhihu/Matisse" \\
|| miss "origin remote is not zhihu/Matisse (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
&& ok "license is Apache-2.0" \\
|| miss "license drift — was Apache-2.0 at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "matisse/src/main/java/com/zhihu/matisse/Matisse.java" \\
&& ok "matisse/src/main/java/com/zhihu/matisse/Matisse.java" \\
|| miss "missing critical file: matisse/src/main/java/com/zhihu/matisse/Matisse.java"
test -f "matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java" \\
&& ok "matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java" \\
|| miss "missing critical file: matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java"
test -f "matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java" \\
&& ok "matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java" \\
|| miss "missing critical file: matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java"
test -f "matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java" \\
&& ok "matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java" \\
|| miss "missing critical file: matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java"
test -f "matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java" \\
&& ok "matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java" \\
|| miss "missing critical file: matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 1119 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1089d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/zhihu/Matisse"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Matisse is a polished Android library for local image and video selection that integrates directly into apps as an Activity/Fragment-based UI flow. It handles JPEG/PNG/GIF images and MPEG/MP4 videos with built-in themes (Zhihu light and Dracula dark), pluggable image loaders (Glide/Picasso), and custom filter rules—solving the pain of building a media picker UI from scratch. Single-module library structure: matisse/src/main/java/com/zhihu/matisse/ contains core entry points (Matisse.java, SelectionCreator.java), pluggable engine layer (engine/impl/ with GlideEngine and PicassoEngine), entity models (internal/entity/), data loaders (internal/loader/ for AlbumLoader, AlbumMediaLoader), and UI activities (`internal/ui/AlbumPreviewActivity.java).
👥Who it's for
Android app developers (Activity/Fragment level) who need to let users pick images or videos from device storage without writing custom media picker UI. Particularly used by Chinese app developers (Zhihu origin) who want a polished, theme-able picker component.
🌱Maturity & risk
Actively maintained and production-ready. Published to Bintray with CI via Travis CI (.travis.yml present), two complete themed implementations in screenshots, and clear versioning. However, built with Gradle 3.5.1 (2019-era), suggesting it may not be under active weekly development anymore.
Low risk for production use but aging dependencies: Gradle 3.5.1 is from 2019, and no test files visible in the top 60 structure suggests weak test coverage. Single-maintainer risk (Zhihu org) is typical for Chinese open-source. The dual image loader pattern (Glide vs Picasso with ProGuard rules) adds complexity.
Active areas of work
No recent activity signals visible in provided data (no recent commits, PRs, or milestones mentioned). The .travis.yml and bintray-release plugin suggest CI/CD was set up but repo appears in maintenance mode rather than active feature development.
🚀Get running
git clone https://github.com/zhihu/Matisse.git
cd Matisse
./gradlew build
Then add implementation 'com.zhihu.android:matisse:$latest_version' to your app's build.gradle and sync.
Daily commands:
This is a library, not an app. To test locally: ./gradlew assembleDebug or ./gradlew test (if tests exist). Import sample code from README into an Android project and call Matisse.from(activity).choose(...).forResult(REQUEST_CODE) then handle in onActivityResult().
🗺️Map of the codebase
matisse/src/main/java/com/zhihu/matisse/Matisse.java— Main entry point and builder API for initializing the image/video selector; every integration starts here.matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java— Fluent API for configuring selection behavior (mime types, count limits, themes); essential for understanding configuration patterns.matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java— Global configuration holder that persists user preferences across the entire selection flow; core state management.matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java— Main activity container that orchestrates album selection and media browsing; entry point for the UI layer.matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java— Fragment displaying the grid of images/videos with selection logic; handles the core media picking UI.matisse/src/main/java/com/zhihu/matisse/engine/ImageEngine.java— Abstract interface for pluggable image loading (Glide/Picasso); allows customization of image rendering pipeline.matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java— Manages the collection of selected items with validation; critical for tracking selection state and constraints.
🛠️How to make changes
Add a Custom Theme
- Create color resource files in matisse/src/main/res/color/ (e.g., my_theme_bottom_toolbar_apply.xml) (
matisse/src/main/res/color/) - Add theme constant to MimeType.java enum or extend SelectionSpec.java with new theme fields (
matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java) - Update SelectionCreator.java theme() method to apply your color resources (
matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java) - Document theme usage in README following the zhihu_style or dracula_style patterns (
README.md)
Add a Custom Image Loader
- Extend ImageEngine.java abstract class implementing loadThumbnail() and loadImage() methods (
matisse/src/main/java/com/zhihu/matisse/engine/ImageEngine.java) - Place implementation in matisse/src/main/java/com/zhihu/matisse/engine/impl/ directory (
matisse/src/main/java/com/zhihu/matisse/engine/impl/) - Add imageEngine() method call to SelectionCreator to allow users to set your loader (
matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java) - Update build.gradle to include your loader's dependency as optional (
matisse/build.gradle)
Add a Custom Selection Filter
- Extend Filter.java abstract class implementing filter() method to validate items (
matisse/src/main/java/com/zhihu/matisse/filter/Filter.java) - Add filter chain logic in SelectedItemCollection.java to apply all registered filters (
matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java) - Expose filter() method in SelectionCreator to allow users to add custom filters (
matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java) - Show IncapableDialog when filter rejects an item (see widget/IncapableDialog.java) (
matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java)
Customize Media Selection Behavior
- Modify SelectionCreator.java to add new configuration methods (e.g., maxSelectionCount, allowMultiple) (
matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java) - Store new settings in SelectionSpec.java singleton (
matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java) - Update MediaSelectionFragment.java to respect new settings when enabling/disabling items (
matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java) - Validate constraints in SelectedItemCollection.java before adding/removing items (
matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java)
🪤Traps & gotchas
- Runtime Permissions: Code requires READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE (API 23+), but the library does NOT handle runtime permission requests—caller must do this before launching. 2) CaptureStrategy: Photo capture (camera) requires FileProvider setup via CaptureStrategy; missing this silently fails. 3) ProGuard rules are counterintuitive: If using Glide, obfuscate Picasso (dontwarn Picasso), and vice versa—this is intentional but unintuitive. 4) SelectionSpec is a Singleton: State persists across multiple Matisse launches in same process; clear it carefully.
🏗️Architecture
💡Concepts to learn
- ContentProvider Query Pattern — Matisse queries MediaStore via ContentProvider (AlbumMediaLoader) to fetch device media; understanding query selectors, sort orders, and URI schemes is essential to modifying media loading logic.
- AsyncTaskLoader — AlbumMediaLoader and AlbumLoader extend AsyncTaskLoader to load albums/media off the main thread without blocking UI; understanding its lifecycle is critical to fixing loading bugs.
- CursorAdapter & ListView Binding — Media grids bind results from ContentProvider cursors; the adapter pattern is central to how selected items are rendered and updated.
- FileProvider & Media Capture — CaptureStrategy wraps FileProvider URIs for camera/video capture; misconfiguration breaks photo/video capture workflows in the picker.
- Theme Inheritance & Attribute Resolution — Matisse_Zhihu and Matisse_Dracula themes inherit and override attributes; understanding Android theme merging is necessary to create custom themes without overriding all attributes.
- Strategy Pattern for Image Loading — ImageEngine interface decouples the picker from specific image loaders (Glide vs Picasso); swapping implementations requires only changing one line in SelectionCreator.
- MIME Type Filtering & File Selection — MimeType enum and Filter classes control which files appear; filters run on both album selection and final validation, affecting which media the user can pick.
🔗Related repos
bumptech/glide— Default image loader implementation (GlideEngine in matisse); many Matisse users integrate Glide.square/picasso— Alternative image loader implementation (PicassoEngine); users can swap Glide for Picasso.google/material-components-android— Material Design themes and components; Matisse's built-in themes (Zhihu/Dracula) extend Material styles.androidx/androidx— Matisse relies on AndroidX APIs (AppCompatActivity, Fragment, Loader, ContentProvider integration).guolindev/giffun— Similar Chinese media picker library; GifFun inspired or competed with Matisse in the same ecosystem.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add instrumented tests for AlbumMediaAdapter and RecyclerViewCursorAdapter
The adapter classes in matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/ lack test coverage. These adapters are critical for rendering media items and handling selection state. Adding instrumented tests would ensure the adapters correctly bind data, handle cursor operations, and manage selection states across configuration changes.
- [ ] Create matisse/src/androidTest/java/com/zhihu/matisse/internal/ui/adapter/ directory
- [ ] Add AlbumMediaAdapterTest.java with tests for item binding, click handlers, and selection updates
- [ ] Add RecyclerViewCursorAdapterTest.java with tests for cursor operations and data refresh
- [ ] Configure test runners in build.gradle and add test dependencies (AndroidX Test, Robolectric)
Add GitHub Actions CI workflow to replace .travis.yml
The repo uses Travis CI configured in .travis.yml, which has been deprecated. A modern GitHub Actions workflow would provide faster, more reliable CI/CD directly integrated with GitHub. This should build the library, run tests, and validate code quality with checkstyle.
- [ ] Create .github/workflows/android-ci.yml with jobs for gradle build, unit tests, and checkstyle validation
- [ ] Configure the workflow to trigger on push and pull requests to main/master branch
- [ ] Add gradle caching to speed up builds and set appropriate JDK version matching build.gradle
- [ ] Update README.md to replace Travis CI badge with GitHub Actions badge pointing to the new workflow
Add unit tests for SelectionSpec and MimeType filtering logic
The SelectionSpec.java and MimeType.java classes contain configuration and filtering logic that determines which media types are selectable and how selection constraints are applied. These are critical business logic classes but have no test coverage. Unit tests would validate mime type filtering, selection constraints, and configuration validation.
- [ ] Create matisse/src/test/java/com/zhihu/matisse/ directory structure
- [ ] Add MimeTypeTest.java with tests for mime type constants, matching logic, and edge cases
- [ ] Add SelectionSpecTest.java with tests for selection limits, mime type filtering, and constraint validation
- [ ] Update build.gradle with testImplementation dependencies (JUnit, Mockito) and add test tasks
🌿Good first issues
- Add unit tests for
matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectedItemCollection.java(selection state logic) andFilter.javaimplementations—test coverage is absent. - Document camera/capture flow in README with working CaptureStrategy example code (currently only images/video picker are shown).
- Add support for custom MIME types beyond the hardcoded JPEG/PNG/GIF/MPEG/MP4 in
MimeType.javaby making the enum extensible.
⭐Top contributors
Click to expand
Top contributors
- @REBOOTERS — 66 commits
- @gejiaheng — 6 commits
- @mthli — 4 commits
- @venkat20390 — 4 commits
- @marcinkunert — 3 commits
📝Recent commits
Click to expand
Recent commits
21591ae— add lang vietnamese (#697) (devbalo93)d956453— closes #676, closes #466, closes #167, cloese #602, closes #659, closes #646, closes #603, closes #602, closes #667 (REBOOTERS)8013a71— bump matisse to 0.5.3-beta2 (REBOOTERS)c077c0e— update readme (REBOOTERS)6dc2b93— fix codestyle issue (REBOOTERS)8925ee6— delete useless code (REBOOTERS)20faf32— fix issue (REBOOTERS)4856c98— Android q (#665) (yccheok)21e0614— upgrade glide to 4.9.0 (REBOOTERS)caad4c9— Add configuration for enable to show preview (Issue #590) (#678) (minibugdev)
🔒Security observations
The Matisse library has several significant security concerns primarily related to outdated dependencies and deprecated build infrastructure. The Android Gradle plugin (3.5.1) and bintray-release (0.9.1) are both unmaintained and pose security risks. The use of the deprecated jcenter() repository prevents access to security updates. While the codebase structure suggests proper separation of concerns for file/media handling, validation of file system operations should be verified. Immediate actions required: update Gradle plugin to latest version, migrate from Bintray to Maven Central, and remove jcenter() repository. Security score reflects critical dependency issues that prevent secure builds and updates.
- High · Outdated Gradle Build Plugin —
build.gradle - classpath 'com.android.tools.build:gradle:3.5.1'. The project uses Android Gradle plugin version 3.5.1, which was released in 2019 and is significantly outdated. This version lacks critical security patches and bug fixes available in current versions. Fix: Update to the latest stable Android Gradle plugin version (currently 8.x). Run 'gradle wrapper --gradle-version=<latest>' and update build.gradle dependencies accordingly. - High · Outdated Bintray Plugin —
build.gradle - classpath 'com.novoda:bintray-release:0.9.1'. The project uses bintray-release version 0.9.1 from 2019. Bintray was sunset by JFrog in 2021, and this plugin is no longer maintained. Using unmaintained build tools poses security risks. Fix: Migrate to JFrog's official Artifactory plugin or Maven Central publishing. Remove the bintray-release dependency and update the publishing configuration. - Medium · Use of jcenter() Repository —
build.gradle - repositories { jcenter() }. The project configures jcenter() as a repository, which was officially shut down by JFrog on May 1, 2021. Continuing to rely on this repository may result in build failures and prevents access to security updates. Fix: Remove jcenter() from all build.gradle files. Use mavenCentral() and Google Maven repository instead: 'https://repo1.maven.org/maven2/' and 'https://maven.google.com/' - Medium · Missing ProGuard Configuration for Library —
matisse/proguard-rules.pro. While a proguard-rules.pro file exists, there is no evidence of adequate obfuscation rules for sensitive components or proper configuration for the image loading libraries (Glide/Picasso). This could expose sensitive metadata or implementation details. Fix: Review and enhance ProGuard rules to properly obfuscate library code, especially for internal implementations. Ensure custom image engine implementations and URI handling are protected. - Medium · File System Access Without Proper Validation —
matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java, PathUtils.java. The MediaStoreCompat.java and PathUtils.java utilities handle file system operations and paths. Without visible validation of file paths and URIs, there may be risks for directory traversal or access to unintended locations. Fix: Implement strict validation for all file paths and URIs. Use canonical path resolution and whitelist allowed directories. Verify permissions before file access operations. - Low · Missing CONTRIBUTING.md Security Guidelines —
Repository root - Missing SECURITY.md. While CONTRIBUTING.md exists, there is no visible security policy or vulnerability disclosure process in the repository (no SECURITY.md file). Fix: Add a SECURITY.md file with responsible vulnerability disclosure guidelines. Include reporting procedures and security contact information.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.