Quick Start
Integrate ANIP SDK into your project.
Deploy Dependency
ANIP SDK's dependency is published in Maven Central and our public repository. You can use the following method to configure repositories.
We recommend using Kotlin DSL as the Gradle build script language.
Configure repositories in your project's build.gradle.kts.
repositories {
google()
mavenCentral()
// (Optional) You can add this URL to use our public repository
// When Sonatype-OSS fails and cannot publish dependencies, this repository is added as a backup
// For details, please visit: https://github.com/HighCapable/maven-repository
maven("https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases")
}You can add this module to your project using the following method.
Version Catalog (Recommended)
Add the dependency to your project's gradle/libs.versions.toml.
[versions]
anip-sdk = "<version>"
[libraries]
anip-sdk = { module = "com.highcapable.anip:anip-sdk", version.ref = "anip-sdk" }Configure the dependency in your project's build.gradle.kts.
dependencies {
implementation(libs.anip.sdk)
}Replace <version> with the version displayed at the top of this section.
Traditional Method
Configure the dependency in your project's build.gradle.kts.
dependencies {
implementation("com.highcapable.anip:anip-sdk:<version>")
}Replace <version> with the version displayed at the top of this document.
Function Introduction
You can view the KDoc click here, or click here to view the corresponding demo project to better understand how to use the SDK.
Create an Instance
Anip is the unified entry point for creating the SDK. Each instance maintains one cache directory and one set of in-memory state. You can create a single instance when the app starts and reuse it throughout the app.
The following example
val anip = Anip(context)If you need to specify a system variant, cache directory, or remote source, pass an AnipConfig.
The following example
val config = AnipConfig(
// Specifies the system variant used to merge vendor system icons.
systemVariant = SystemVariant.MIOS,
// Explicitly specifies the cache directory.
cacheDirectory = context.cacheDir.resolve("your-anip-resources"),
// Specifies the remote resource source.
source = RemoteSource.GitHub(
repository = RemoteSource.GITHUB_OFFICIAL_REPO_SLUG
)
)
val anip = Anip(context, config)systemVariant defaults to null, which loads no system icons. COMMON loads only the common system icons, while MIOS (HyperOS/MIUI) and COLOROS load both common and the corresponding vendor system icons.
The current Anip instance keeps a reference to the configuration object. After modifying its properties, the latest configuration will be read directly during the next update. Icons already loaded into memory will not be automatically replaced when the configuration changes.
Load and Update Resources
The following APIs are provided by the SDK for loading and updating resources.
| API | Behavior |
|---|---|
reload | Loads only the latest valid resources from the cache directory without network requests, returning true on success. |
fetch | Reads the remote release manifest, downloads and replaces the cache when resources have changed, then updates the in-memory icon rules. |
getIcon / getIcons | Queries only the current instance's in-memory state, returning null or an empty list. |
We recommend reading the existing cache at startup before checking for updates.
The following example
anip.reload()
val cachedIcons = anip.getIcons()
val result = anip.fetch()
val latestIcons = anip.getIcons()These loading APIs are all marked as suspend, and file and network operations are dispatched by the SDK to the IO thread.
timestamp represents the resource commit time of the most recent successful synchronization. It defaults to 0L and is persisted as text in the timestamp file under the cache directory.
Update Result
fetch returns an Anip.FetchResult, where status has the following three states.
| Status | Meaning |
|---|---|
SUCCESS | New resources have been downloaded and loaded. |
UP_TO_DATE | Local resources are already up to date. |
FAILED | Querying, downloading, verification, or loading failed. |
Anip.FetchResult also provides isOk and message. isOk returns true when the status is not FAILED, while message describes the update result or failure reason.
The following example
when (result.status) {
Anip.FetchResult.Status.SUCCESS -> {
// Do something.
}
Anip.FetchResult.Status.UP_TO_DATE -> {
// Do something.
}
Anip.FetchResult.Status.FAILED -> showError(result.message)
}Query Icons
You can query a single icon directly by package name, or obtain a list by category.
The following example
val icon = anip.getIcon("com.example.app")
val apps = anip.getIcons(IconCategory.APP)
val games = anip.getIcons(IconCategory.GAME)
val systemIcons = anip.getIcons(IconCategory.SYSTEM)
val allIcons = anip.getIcons()Each NotificationIcon provides the following data.
packageName: App package namelabel: App name resolved according to the current system languageavailableLabels: All localized names included in the rule, represented asMap<Locale, String>color: Color used by the icon, ornullwhen not setoverlay: Whether to force overlay all notification icons pushed by the app, regardless of whether they are native monochromecontributors: List of contributor names
Icon bitmaps are not decoded all at once with the entity list. When using one individually, please load it explicitly via loadBitmap. A decoding failure returns null, while a successful result enters the LRU memory cache held by the current Anip instance.
The following example
val bitmap = anip.getIcon("com.example.app")?.loadBitmap()Create an In-Memory Snapshot
If you need to use the complete icon set intensively over a long period, you can create an immutable NotificationIconSnapshot.
The following example
val snapshot = anip.createSnapshot(IconCategory.ALL)
val icon = snapshot.getIcon("com.example.app")
val bitmap = snapshot.getBitmap("com.example.app")The snapshot loads icons in the selected category sequentially, keeps only entries that decode successfully, and caches all Bitmap instances. memorySizeBytes measures usage by deduplicating actual Bitmap objects, so inherited rules or multiple package names using the same resource are not counted repeatedly.
The snapshot remains unchanged after construction. Later calls to fetch or reload do not modify an existing snapshot. You can finish creating a new snapshot before replacing the old reference.
After the snapshot reference is released, it is reclaimed by GC. There is no need to manually call recycle or close.
Notice
createSnapshot explicitly incurs the memory cost of loading the complete icon set. If you only need to obtain a few icons occasionally, please use NotificationIcon.loadBitmap directly.
Configure Remote Sources
RemoteSource provides the following remote source templates.
| Template | Behavior |
|---|---|
| GitHub | Reads the resource manifest from the latest GitHub repository Release; defaults to the official repository. |
| GitLab | Reads the resource manifest from the latest GitLab project Release; supports custom host addresses. |
| Static | Appends anip-release.json after the base URL. |
| Custom | Uses the complete resource manifest URL directly. |
The following example
val github = RemoteSource.GitHub("owner/repository")
val gitLab = RemoteSource.GitLab("group/project")
val static = RemoteSource.Static("https://cdn.example.com/anip")
val custom = RemoteSource.Custom("https://resources.example.com/anip-release.json")
config.source = customWhen customizing sources, it needs to provide an ANIP-compliant anip-release.json and its corresponding resource Bundle.
Customize Request URLs
Every template accepts a RemoteSource.UrlResolver. Before requesting the resource manifest or Bundle, it receives the original URL and returns the final request URL.
The following example
val source = RemoteSource.GitHub(
urlResolver = RemoteSource.UrlResolver { sourceUrl, _, _ ->
"https://some.domain/$sourceUrl"
}
)If tag is null for a manifest request, a Bundle request provides both tag and asset name, allowing each resource to be mapped to a self-hosted CDN separately.
The following example
val source = RemoteSource.Custom(
manifestUrl = "https://origin.example.com/anip-release.json",
urlResolver = RemoteSource.UrlResolver { _, tag, assetName ->
if (tag == null)
"https://cdn.example.com/anip-release.json"
else "https://cdn.example.com/releases/$tag/$assetName"
}
)Notice
Static URL concatenation should be performed in the callback. You cannot execute network or time-consuming requests in the callback.
Returning a blank or invalid URL will cause failure directly.
Cache and Multiple Processes
When cacheDirectory is not specified, the SDK uses anip-icon-resources under the app cache directory.
If you are using the SDK in an Xposed module and need to share resources between the module process and host process, the caller can configure one directory that both sides can access. The process responsible for networking executes fetch, while other processes execute reload after receiving a refresh notification. Or, if both processes have network access, each process can maintain its own cache directory and notify the other to update.
The SDK does not include any directory permission handling functions. It only uses standard file APIs, and you need to handle directory authorization and cross-process synchronization yourself.