简体中文 / [English]


A Record of Pitfalls Encountered When Using Content Provider for Cross-App Communication in Xposed Hooking

 

This article is currently an experimental machine translation and may contain errors. If anything is unclear, please refer to the original Chinese version. I am continuously working to improve the translation.

Recently, I’ve been experimenting with using Xposed to hook into other apps.

Try 0x00

While writing code, I ran into an issue: after hooking with Xposed, the code runs under the identity of the target app being hooked. However, the SharedPreferences saved by my MainActivity belong to my own plugin’s app context, and thus aren’t accessible during the hook—due to permission restrictions. Unfortunately, MODE_WORLD_READABLE for SharedPreferences has been completely removed in Android 7.0+, and attempting to use it throws an exception directly.

Try 0x01

I then considered using Android’s recommended standard method—ContentProvider—for data sharing. But here’s the catch: within the hooked app, it’s not convenient to obtain a Context object to access the ContentResolver…

Try 0x02

Next, I tried direct method calls between components. For example:

My GUI main app is me.lyc8503.activity.MainActivity, and I wanted to directly access my hook logic in me.lyc8503.Hook.Hooktest from this MainActivity. But this immediately threw a java.lang.NoClassDefFoundError.

Likewise, trying to access MainActivity code directly from the hooking code resulted in various NullPointerExceptions due to uninitialized objects.

Try 0x03

Later, I discovered that XSharedPreferences has a method called makeWorldReadable(), which I initially thought might be a workaround for newer Android security restrictions. I spent quite some time trying it out—only to later check the documentation:

public boolean makeWorldReadable ()

Tries to make the preferences file world-readable.

Warning: This is only meant to work around permission “fix” functions that are part of some recoveries. It doesn’t replace the need to open preferences with MODE_WORLD_READABLE in the module’s UI code. Otherwise, Android will set stricter permissions again during the next save.

This will only work if executed as root (e.g. initZygote()) and only if SELinux is disabled.

Returns
  • true if the file was successfully made world-readable.

Needless to say, this wasn’t a viable solution for general use.

Try 0x04 Final

Eventually, I came back to Android’s recommended approaches: Broadcast and ContentProvider.

Since this communication is only between my UI and the hooked code, using Broadcast felt a bit overkill or less suitable. So I finally settled on ContentProvider.

The root cause of all these issues? My insufficient understanding of Context. I mistakenly assumed that all Context instances had to come directly from Activity.getContext(). Eventually, I found this article enlightening.

Context说明Context说明

As shown, Activity, Service, and Application are all subclasses of Context;

From Android’s perspective, Context represents a “scene” — a process of interaction with the operating system. From a programming standpoint, Context is an abstract class, and classes like Activity, Service, and Application are its implementations.

Looking closer at the diagram: Activity, Service, and Application all inherit from ContextWrapper, which internally holds a base context that actually implements most of the methods.

Here’s another diagram showing capabilities across different Context types:

Context功能Context功能

You may notice some “NO” entries marked with numbers. Technically, these operations are possible (hence “YES” in capability), but they’re marked “NO” for practical reasons. Let me explain:

Number 1: Starting an Activity is technically possible in these contexts, but it requires creating a new task. This is generally not recommended.

Number 2: Layout inflation is allowed here, but it will use the system default theme. Any custom styles you’ve defined might not be applied.

Number 3: Allowed only when receiver is null, and only on Android 4.2+, to retrieve the current sticky broadcast value. (This can be ignored in most cases.)

Note: ContentProvider and BroadcastReceiver are included in the table because their callback methods provide a Context for use.

Now, focus on the comparison between Activity and Application. You’ll notice that almost all UI-related methods are discouraged or unsupported with Application. In fact, any operation related to the UI should be performed using an Activity context. For non-UI operations, instances of Service, Activity, or Application can generally be used—just be careful about holding Context references to avoid memory leaks.

So in the end, all I needed was AndroidAppHelper.currentApplication().getApplicationContext() to obtain an Application Context, which could then be used to acquire a ContentResolver.

This post serves as a reminder of the pitfalls caused by an incomplete understanding of Context.

This article is licensed under the CC BY-NC-SA 4.0 license.

Author: lyc8503, Article link: https://blog.lyc8503.net/en/post/xposed-cross-application-communication/
If this article was helpful or interesting to you, consider buy me a coffee¬_¬
Feel free to comment in English below o/