Mobile Development 18 min read

Understanding Permissions4M: An Android Compile‑Time Annotation Framework for Runtime Permissions

This article explains the design and implementation of Permissions4M, an Android library that uses compile‑time annotations to simplify runtime permission handling, covering the pre‑compilation, compilation, and post‑compilation stages, module structure, key code snippets, and usage patterns.

Qunar Tech Salon
Qunar Tech Salon
Qunar Tech Salon
Understanding Permissions4M: An Android Compile‑Time Annotation Framework for Runtime Permissions

The author introduces Permissions4M, an open‑source Android library that leverages compile‑time annotation processing to manage Android 6.0+ runtime permissions, and recommends readers be familiar with annotations and runtime permissions before proceeding.

During the pre‑compilation stage, the source code contains two parts: the annotation declarations (e.g., @BindView) and the API calls (e.g., ButterKnife.bind(this) ). The author emphasizes separating these concerns to keep the library modular.

In the compilation stage, an annotation processor scans each Activity , extracts annotation information, and generates a proxy class. Three modules are required: an API module, an annotation module, and an annotation‑processor module.

After compilation, the generated proxy class is invoked by the API module to perform permission callbacks such as @PermissionsGranted , @PermissionsDenied , and @PermissionsRationale . The author shows how the proxy maps request codes to methods.

Module structure :

• permissions4m-annotation – a Java module containing only annotation definitions.

• permissions4m-processor – a Java module that implements the annotation processor.

• permissions4m-api – an Android library module that provides the public API for developers.

Annotation definition example:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PermissionsGranted {
    int[] value();
}

Processor skeleton:

private Elements mUtils;
private Filer mFiler;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    mUtils = processingEnv.getElementUtils();
    mFiler = processingEnv.getFiler();
}

@Override
public Set
getSupportedAnnotationTypes() {
    Set
set = new HashSet<>(5);
    set.add(PermissionsGranted.class.getCanonicalName());
    set.add(PermissionsDenied.class.getCanonicalName());
    set.add(PermissionsRationale.class.getCanonicalName());
    set.add(PermissionsCustomRationale.class.getCanonicalName());
    set.add(PermissionsRequestSync.class.getCanonicalName());
    return set;
}

@Override
public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
}

The core process method extracts annotated methods, builds a ProxyInfo for each class, and records request codes and method names.

private boolean isAnnotatedWithMethod(RoundEnvironment roundEnv, Class
clazz) {
    Set
elements = roundEnv.getElementsAnnotatedWith(clazz);
    for (Element element : elements) {
        if (isValid(element)) { return false; }
        ExecutableElement method = (ExecutableElement) element;
        TypeElement typeElement = (TypeElement) method.getEnclosingElement();
        String typeName = typeElement.getQualifiedName().toString();
        ProxyInfo info = map.get(typeName);
        if (info == null) {
            info = new ProxyInfo(mUtils, typeElement);
            map.put(typeName, info);
        }
        // handle annotation values and fill maps
    }
    return true;
}

Proxy class generation builds a Java source file that implements the PermissionsProxy interface.

StringBuilder builder = new StringBuilder();
builder.append("package ").append(packageName).append(";\n\n")
       .append("import com.joker.api.*;\n\n")
       .append("public class ").append(proxyName)
       .append(" implements ").append(PERMISSIONS_PROXY)
       .append("<").append(element.getSimpleName()).append("> {\n");

The PermissionsProxy interface defines the callbacks used by the generated proxy:

public interface PermissionsProxy
{
    void rationale(T object, int code);
    void denied(T object, int code);
    void granted(T object, int code);
    boolean customRationale(T object, int code);
    void startSyncRequestPermissionsMethod(T object);
}

The API module exposes methods such as syncRequestPermissions(Activity) , syncRequestPermissions(Fragment) , and requestPermission(...) . These methods check the Android version, instantiate the appropriate proxy via reflection, and delegate permission handling to the proxy’s generated methods.

Overall, the article walks the reader through the full lifecycle of a compile‑time annotation library for Android permissions, from module setup and annotation definitions to processor implementation, proxy generation, and runtime usage.

Javamobile developmentAndroidannotation-processingRuntime Permissionscompile-timePermissions4M
Qunar Tech Salon
Written by

Qunar Tech Salon

Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.