Multidex support #30

Open
moneytoo opened this Issue Dec 31, 2014 · 21 comments

Projects

None yet

4 participants

@moneytoo

Based on various comments/threads (see below) I understand there's no support for Multidex in Xposed. Most probably get in touch with it in Google Play Services which is over one dex in size.

Though there's nice workaround for GMS by @kmark: https://gist.github.com/kmark/d6a19bc50c6ba7cc8f1f it would be nice to know if multidex support is something that can be expected from Xposed.

http://forum.xda-developers.com/xposed/loading-class-classes2-dex-t2852950
http://forum.xda-developers.com/xposed/multidex-support-t2963645
https://github.com/rsteckler/unbounce-android#why-was-this-such-a-pain-to-fix

@rovo89
Owner
rovo89 commented Jan 1, 2015

I haven't worked with multidex apps yet, but I see no reason why it shouldn't work if the correct classloader is used at the correct time. Check this out:
https://developer.android.com/tools/building/multidex.html#mdex-pre-l
https://developer.android.com/tools/support-library/features.html#multidex

I assume it will work out of the box with ART. For Dalvik, there is no multidex support in pure Android, apps need to use the support library. As far as I understand the source code of that library, it extracts the additional dex files and adds them to the existing classloader via reflection. The app needs to trigger this in Application.attachBaseContext(), or use the MultiDexApplication class which already does this.

handleLoadPackage() is called as early as possible to allow modules to modify as much as possible. At that point, the app doesn't know anything about the additional dex files yet, so obviously you can't access those classes either. It's similar to apps loading additional dex files themselves (I think Facebook does), either by creating new classloaders or extending existing ones.

It's not an option to execute that callback later, as it would break existing modules. The workaround you have shown basically is the fix already. For the current implementation of the support library, it would be the cleanest way to hook MultiDex.install() and place further hooks after it has been executed. We could discuss whether the Xposed framework could offer a helper for this, but chances are that there have been and will be different implementations that don't work like this. Some apps might not follow that advice, but call the method later. Also, Proguard might obfuscate the names.

It really depends on the target app how early you need to place the hook. For most, I guess it's ok to hook something during the early stages of the Application lifecycle.

@rovo89
Owner
rovo89 commented Jan 1, 2015

At least for those apps following the recommendation, I think the best (early and generic) place to hook is Application.attach(): https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/Application.java#L180

This calls attachBaseContext(), which would be overridden in case of Multidex, so hooking Application.attachBaseContext() would not be enough. Placing hooks after the attach() method has finished should work fine. But again, the only thing the framework could offer would be a helper which places this particular hook. Doesn't seem like a big saving.

@pylerSM
Contributor
pylerSM commented Jan 1, 2015

I think helpers are definitely good thing. It's more developer-friendly. Not every developer may know what exactly needs to be hooked (in this case - Application.attach() (as you said)), but simple helper method may be helpful.

@rovo89
Owner
rovo89 commented Jan 1, 2015

Well, but from a helper method, you'd expect that it works in all or at least most cases. I already listed some limitations, such as apps not following the recommendations or using their own ways to load additional classes. It's easy to add new APIs, but hard to get rid of them, even if they were bad. I suggest that people test it first with something like this in handleLoadPackage() for their target app (untested):

findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        // place your hooks here, it should work with lpparam.classLoader
    }
});

If that works fine as expected in (almost) all cases, we can think about a helper method. But even then, the code wouldn't be shorter, and developers would still need to know when to use it.

@pylerSM
Contributor
pylerSM commented Jan 1, 2015

Maybe better solution is put this somewhere in Xposed Wiki and make good description of it.

@rovo89
Owner
rovo89 commented Jan 1, 2015

Yes. I've wanted to have a "best practices" page (or multiple pages) for a long time, about good style, efficency, tricks, .... But you know how it is with the documentation. ;)

@pylerSM
Contributor
pylerSM commented Jan 1, 2015

I hate writing docs or "how to do" manuals :D Maybe it's time to create something like that. Module developers could write their tricks etc. Basically, I shared this idea on XDA: http://forum.xda-developers.com/xposed/xposed-wiki-t2986692. But yet no answer.

But I think there should be TODO list for Xposed. What needs to be done for next release, etc..

@kmark
kmark commented Jan 1, 2015

Thanks, rovo. I appreciate the work on this! In my original gist, hooking onCreate was purely to avoid ProGuard. attach definitely looks better though. As you mentioned it's probably not a good idea to have helpers that only work sometimes, and more importantly, do not reliably report failure when it does not work.

Putting significant effort into documentation might not be a good use of time unless Xposed for ART has a backwards compatible API. And also defining the "target audience" is a big part of writing documentation like this. Probably not worth writing the docs as if they're for Android/Java beginners.

@pylerSM
Contributor
pylerSM commented Jan 1, 2015

I head on XDA that there might be problems with XSharedPreferences on ART. I hope @rovo89 knows how to solve it :)

@kmark
kmark commented Jan 2, 2015

@pylerSM Not to get off topic but the only problems I've experienced with XSharedPreferences were permission related and not necessarily bugs with the XSP class itself. "Unfortunately" Android prefers and defaults to MODE_PRIVATE for PreferenceManager instances and consequently PreferenceActivity instances. There's no way to set the PreferenceManager's SharedPreferences mode from a PrefAct since its PrefManager field is private. I avoided XSP altogether and used a ContentProvider. It may be overkill but works pretty nicely. I do need to refactor that module though.

Ironically you could probably use Xposed hooks to overcome the above limitations...

@pylerSM
Contributor
pylerSM commented Jan 2, 2015

getPreferenceManager().setSharedPreferencesMode(MODE_WORLD_READABLE);
addPreferencesFromResource(R.xml.preferences);

This is not working for you? I use this code in my modules and their settings work well.

@rovo89
Owner
rovo89 commented Jan 2, 2015

Putting significant effort into documentation might not be a good use of time unless Xposed for ART has a backwards compatible API. And also defining the "target audience" is a big part of writing documentation like this. Probably not worth writing the docs as if they're for Android/Java beginners.

Similar? The API would be exactly the same. ;) There might be some things to consider like for every new, big release, however that's not caused by ART but by other platform changes...

I head on XDA that there might be problems with XSharedPreferences on ART.

People seem to think Lollipop == ART, but ART is only one of the challenges. SELinux indeed makes things more complicated, you can't open every file from everywhere even if it's world-readable.

Anyway, the answer to whether Xposed supports multidex is in my comment above, and I recommend to try and use the example code I provided there.

@rovo89 rovo89 added the question label Jan 2, 2015
@kmark
kmark commented Jan 2, 2015

getPreferenceManager().setSharedPreferencesMode(MODE_WORLD_READABLE);
addPreferencesFromResource(R.xml.preferences);

This is not working for you? I use this code in my modules and their settings work well.

Oops. I missed that method. Might still be problematic if the preference XML file was created/added/restored without having the modified permissions set first. Hopefully calling makeWorldReadable early and often would avoid that. I was honestly surprised to see the MODE_WORLD_X constants still available in API 21.

Similar? The API would be exactly the same. ;) There might be some things to consider like for every new, big release, however that's not caused by ART but by other platform changes...

Great to hear, thanks.

@pylerSM
Contributor
pylerSM commented Jan 2, 2015

Check this https://github.com/pylerSM/XInstaller/blob/master/src/com/pyler/xinstaller/Utils.java#L155

In my module I implemented option to backup and restore settings. So I open prefs in word-readable mode and permissions stay ok.

@kmark
kmark commented Jan 2, 2015

Oh, I was referring to backups/restores done outside of the app like TitaniumBackup or whatever Google's multi-device app syncing feature is called these days.

@pylerSM
Contributor
pylerSM commented Jan 2, 2015

Using prefs.makeWorldReadable() method in every hook would probably solve this issue.

The cleanest way of app backuping for me is adb backup. Google's backup feature in Lollipop is nice, but it requires implementation in every app. I would expect that system would handle it itself since developer could set manifest flag to allow app backup.

@rovo89
Owner
rovo89 commented Jan 2, 2015

Using prefs.makeWorldReadable() method in every hook would probably solve this issue.

Are you aware that this can only be effective when the process is running as root, i.e. in initZygote()?

@pylerSM
Contributor
pylerSM commented Jan 2, 2015

Uhm, I didn't know that it will not work in same cases. Maybe I missed it in docs.

@kmark
kmark commented Jan 2, 2015

makeWorldReadable just calls setReadable(true, false) on the File backing the preferences with the same authority as the caller. So it'll only work from within your app's context or during initZygote

@rovo89
Owner
rovo89 commented Jan 2, 2015

Correct. It was only introduced because some recovery "permission fix" scripts remove the world-readable bit. It's not replacement for using MODE_WORLD_READABLE when opening settings.

I have already added some more documentation locally, I'll just if I also added a clear warning about this.

@kmark
kmark commented Mar 21, 2015

Just gave this a test and @rovo89's Application.attach method worked perfectly. Updated my gist to reflect this. Only disadvantage is that attach is a private API and could change at any time (even between API levels). However given the nature of Android's internals this is unlikely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment