AndroidLife

repository·master·Indexed 43 days ago

https://github.com/camnter/androidlife

A repository of source code notes and educational modules for advanced Android development. It includes dex-method-counts for managing the 64K method limit, the life-plugin Gradle plugin, and frameworks for implementing pluginization via BroadcastReceivers and ContentProviders using ClassLoader patching.

Tokens
44.2K
Snippets
122
Records
162
Agent score
55%

What's inside AndroidLife

  1. Overview of dex-method-counts

    master
    dex-method-counts is a tool used to output per-package method counts in an Android DEX executable. It groups counts by package to help developers manage and stay under the 65,536 referenced method limit (the 64K limit) in Android applications. It supports analyzing .apk, .zip, .dex, or directory inputs.
  2. Explore AndroidLife source code notes and implementations

    master

    AndroidLife serves as a repository of source code notes and implementations for various Android-related libraries and technologies. Developers can use this repository to study the internal workings of popular frameworks and tools.

    Key areas of study include:

    • Core Android Libraries: Notes on Agera, Volley, Otto, and Butterknife.
    • Routing & Dependency Injection: Implementations related to ARouter and various APT (Annotation Processing Tool) exercises.
    • System & Performance: Insights into Instant Run, ANR-WatchDog, and Dex method counting.
    • Pluginization: Deep dives into VirtualApk and various plugin-loading mechanisms.
  3. Explore AndroidLife component examples

    master

    AndroidLife is a collection of Android implementation examples covering various aspects of Android development, including UI components, design patterns, utilities, and system services. Developers can use this repository as a reference for implementing specific features such as:

    • UI & Design Support: TextInputLayout, NavigationView, FloatingActionButton, Snackbar, CoordinatorLayout, and various TabLayout styles.
    • Utilities: DeviceUtil, DateUtil, ImageUtil (for downloading and saving to gallery), ReflectionUtil, and ResourcesUtil.
    • Core Android Concepts: AsyncTask templates, LaunchMode (specifically singleTask), Service (local and AIDL/remote), BroadcastReceiver (static, dynamic, and download), and ContentProvider.
    • Advanced UI: EasySlidingTabs, EasyRecyclerView, EasyFlowLayout, TagTextView (rich text with clickable labels), and Span effects.
    • Data & Concurrency: SQLite demos, RxAndroid usage, and Otto subscription mechanism.
    • Hardware & System: SensorManager and LocationManager.
  4. How to hook AMS (Activity Manager Service)

    master

    To hook the Activity Manager Service (AMS), you must intercept the communication between the local process and the remote AMS via the IActivityManager interface.

    In Android, ActivityManagerNative holds a static Singleton<IActivityManager> named gDefault. The actual instance of the service is stored within the mInstance field of this Singleton object.

    Hooking Strategy:

    1. Create a dynamic proxy implementation of IActivityManager using java.lang.reflect.InvocationHandler.
    2. Use reflection to access the gDefault field in android.app.ActivityManagerNative.
    3. Access the mInstance field within the android.util.Singleton class.
    4. Replace the existing mInstance with your dynamic proxy instance.
    // 1. Implement the handler
    public class IActivityManagerHandler implements InvocationHandler {
        Object base;
        IActivityManagerHandler(Object base) { this.base = base; }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Logic before method call
            final Object object = method.invoke(this.base, args);
            // Logic after method call
            return object;
        }
    }
    
    // 2. Use AMSHooker to perform the replacement
    AMSHooker.hookActivityManagerNative();
  5. Trace the lifecycle flow from system_server to App process

    master

    To hook Activity Manager Service (AMS) for an activity plugin, you must understand the transition between the system_server process and the target App process.

    1. Leaving system_server (Entering App Process)

    The transition begins in ActivityStackSupervisor#realStartActivityLocked. This method calls app.thread.scheduleLaunchActivity(...).

    • app.thread is an instance of ApplicationThread (an inner class of ActivityThread).
    • Calling this method invokes a remote Binder Proxy object, which moves execution from the system_server process to the App process.

    2. Inside the App Process

    Once in the App process, the execution follows this path:

    1. ActivityThread.H#handleMessage receives the LAUNCH_ACTIVITY message.
    2. ActivityThread#handleLaunchActivity is called.
    3. ActivityThread#performLaunchActivity is called.
    4. Instrumentation#callActivityOnCreate is called.
    5. Finally, the Activity#onCreate lifecycle method is executed.

    3. Intercepting the LAUNCH_ACTIVITY message

    In ActivityThread.H, the LAUNCH_ACTIVITY message can be intercepted by leveraging the Handler priority system. The Handler.dispatchMessage method follows this priority:

    1. Highest Priority: msg.callback (attached to the specific message).
    2. Medium Priority: mCallback (the Handler's callback object).
    3. Lowest Priority: handleMessage (the default implementation).

    To intercept the LAUNCH_ACTIVITY message without knowing which specific message object is being sent, you should hook/inject a Handler.Callback into ActivityThread.H#mCallback.

  6. How AndroidManifest validation works in AMS

    master

    Validation of whether an Activity is declared in the AndroidManifest.xml occurs within the system_server process, specifically during the execution of ActivityStarter methods.

    If the Activity is not found in the manifest, ActivityStarter returns ActivityManager.START_CLASS_NOT_FOUND. This result is passed back to the application process, where Instrumentation.checkStartActivityResult catches it and throws an ActivityNotFoundException.

    Error Scenario: If you attempt to start an explicit component that is missing from the manifest, the following error is triggered: Unable to find explicit activity class <component_name>; have you declared this activity in your AndroidManifest.xml?

    // Inside ActivityStarter (system_server)
    if (err == ActivityManager.START_SUCCESS && aInfo == null) {
        err = ActivityManager.START_CLASS_NOT_FOUND;
    }
    
    // Inside Instrumentation (application process)
    case ActivityManager.START_CLASS_NOT_FOUND:
        if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
            throw new ActivityNotFoundException("Unable to find explicit activity class " + ...);
  7. Access plugin ContentProviders via StubContentProvider

    master

    When interacting with a plugin's ContentProvider, you cannot use the plugin's original URI directly. Instead, you must use a stub URI provided by the host.

    The URI format is: StubContentProvider.STUB_URI + / + [Plugin's Full Provider Class Name]

    Example URI Construction: If the plugin provider class is com.camnter.content.provider.plugin.plugin.PluginContentProvider, the URI used by the host to query it would be: content://com.camnter.content.provider.plugin.host.StubContentProvider/com.camnter.content.provider.plugin.plugin.PluginContentProvider

    // Constructing the URI to access the plugin provider
    this.pluginUri = Uri.parse(StubContentProvider.STUB_URI.toString() + '/' + 
        "com.camnter.content.provider.plugin.plugin.PluginContentProvider");
    
    // Using the resolver to interact with the plugin
    this.resolver = this.getContentResolver();
    
    // Querying
    Cursor result = this.resolver.query(this.pluginUri, null, null, null, null);
    
    // Inserting
    ContentValues values = new ContentValues();
    values.put("content", "some data");
    this.resolver.insert(this.pluginUri, values);
    
    // Deleting
    this.resolver.delete(this.pluginUri, null, null);
  8. How to hook PMS (Package Manager Service)

    master

    To hook the Package Manager Service (PMS), you need to intercept the IPackageManager interface. There are two primary locations where this interface is held and can be intercepted:

    1. ActivityThread: The static field static volatile IPackageManager sPackageManager holds the service instance.
    2. ApplicationPackageManager: The private field private final IPackageManager mPM holds the service instance used by the PackageManager wrapper.

    Hooking Strategy:

    1. Prepare a dynamic proxy implementation of IPackageManager.
    2. Replace the sPackageManager field in android.app.ActivityThread.
    3. Replace the mPM field in android.app.ApplicationPackageManager.
  9. Learn Android Pluginization (Plugin Frameworks)

    master

    The repository contains extensive implementations of Android pluginization techniques, ranging from simple class loading to complex system hooking. This is useful for developers building modular architectures or dynamic feature loading systems.

    Pluginization topics covered:

    • Basic Loading: Simple class loading, loading plugin resources (replacing host resources), and multi-classloader loading.
    • Component Implementation: Plugins for registering and opening Activities, Services, BroadcastReceivers, and ContentProviders.
    • System Hooking:
      • Hooking Binder.
      • Hooking AMS (Activity Manager Service) and PMS (Package Manager Service).
      • Hooking LoadedApk to load component classloaders.
    • Resource Management: Merging host and plugin resources and preventing ID conflicts using specific PP segments.
  10. How to hook mResources of ContextImpl to load plugin layouts

    master

    To allow a plugin Activity to access resources (including layouts) from a plugin APK, you must hook the mResources field within ContextImpl.

    Important Note: A plugin Activity cannot access cacheFile before calling super.attachBaseContext(newBase). You must use the newBase context provided in attachBaseContext to retrieve file paths.

    There are two ways to handle plugin resources:

    1. Direct Loading: Load plugin resources directly. This allows accessing raw resources but cannot load layouts via LayoutInflater.
    2. Hooking mResources: By using reflection to replace the mResources field in ContextImpl, you enable the standard Android resource system (including setContentView(R.layout...)) to resolve plugin resources.

    When using custom Views or Fragments, ensure you hold a reference to the inflated view:

    final View selfView = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
    this.setContentView(selfView);
    @Override
    protected void attachBaseContext(Context newBase) {
        try {
            final Field field = newBase.getClass().getDeclaredField("mResources");
            field.setAccessible(true);
            if (this.resources == null) {
                this.resources = this.getPluginResources(newBase);
            }
            field.set(newBase, this.resources);
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.attachBaseContext(newBase);
    }