rovo89 / XposedBridge

The Java part of the Xposed framework.
3.9k stars 1.1k forks source link

Cannot replace method Activity#onCreate #201

Closed davcec closed 7 years ago

davcec commented 7 years ago

I'm trying to hook method onCreate for an Activity class to remove timeout (of 1000 millis) when starting new activity:

public class Activity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                startActivity(new Intent(this, Activity2.class));
                finish();
            }
        }, 1000);
    }
}

Since I cannot simply hook method postDelayed for the inline Handler class:

With the hooks you can place with Xposed, you can't modify the code inside methods (it would be impossible to define clearly what kind of changes you want to do in which place). Instead, you can inject your own code before and after methods, which are the smallest unit in Java that can be addressed clearly. https://github.com/rovo89/XposedBridge/wiki/Development-tutorial

I have to replace method onCreate completely (i.e., there isn't a field for the timeout value I can try to hook etc.):

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    startActivity(new Intent(this, Activity2.class));
}

My problem is I have to invoke super.onCreate(Bundle) on my hook due to SuperNotCalledException throwed at runtime, but I'm not able to achieve this. :-( A naïve solution like this simply doesn't work:

@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
    Object superObject = param.thisObject.getClass().getSuperclass().newInstance();
    XposedHelpers.callMethod(superObject, param.method.getName(), param.args);
    return param.getResult();
}

In fact, I found these answers posted at XDA Developers by @rovo89:

The "super" construct is something slightly different, it allows you to access overridden methods. A Class also has a getSuperclass() method, so you could get that, but it won't help you here. https://forum.xda-developers.com/showpost.php?p=42947833&postcount=1846

Well, it's not possible to call super methods via reflection. So if it is possible at all, then it requires using some internal Dalvik methods to avoid the lookup in the virtual method table for overriding methods. I'm not completely sure whether this would work at all. But if it does, it would become part of Xposed's API, which should also work once I support ART. https://forum.xda-developers.com/showpost.php?p=51645044&postcount=10271

Any advice? Thanks in advance. Cheers.

rovo89 commented 7 years ago

Since I cannot simply hook method run for the inline Handler class:

That's wrong, you can hook that method. However, it's a method in an anonymous class, so it will be called Activity$1.run() or something like that. Just inspect the smali of the app and you'll see it.

davcec commented 7 years ago

Since I cannot simply hook method run for the inline Handler class:

That's wrong, you can hook that method. However, it's a method in an anonymous class, so it will be called Activity$1.run() or something like that.

My bad,.. I updated question with proper method (i.e., Handler#postDelayed). My final goal is to remove the timeout (of 1000 millis). Any advice? Thanks in advance. Cheers.

C3C0 commented 7 years ago

One solution: Simply start that activity yourself in after hook of onCreate. Then the only problem left is to hook anonymous run() to suppress it to make sure it's not executed.

davcec commented 7 years ago

So, I should

  1. use afterHookedMethod instead of replaceHookedMethod when hooking method onCreate to start the new activity;
  2. suppress the method run on the anonymous Runnable class.

Interesting solution, thank you @C3C0. Sadly, I still have the new activity started after timeout... Cheers.

rovo89 commented 7 years ago

Yep, sounds like a good solution.

davcec commented 7 years ago

Why question closed?

Sadly, I still have the new activity started after timeout...

C3C0 commented 7 years ago

Clearly you didn't manage to suppress the run() method. You should hook run() of anonymous Runnable class which you can find out by decompiling; and then use before hook on its run() method to execute param.setResult(null) (or replace it with built-in DO_NOTHING hook).

liudongmiao commented 7 years ago

@davcec There is no way to call super method via reflect. However, you can try the new MethodHandle api, which is not reflection.

And, if you go deeper, it's a cat-dog game. Don't suppose you can always hook an app.

davcec commented 7 years ago

Clearly you didn't manage to suppress the run() method. You should hook run() of anonymous Runnable class which you can find out by decompiling; and then [...] replace it with built-in DO_NOTHING hook).

This is exactly what I did:

XposedHelpers.findAndHookMethod("Activity2$1", lpparam.classLoader, "run", new XC_MethodReplacement() {
    @Override
    protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
        return param.getResult();   // void
    }
});

use before hook on its run() method to execute param.setResult(null)

Well, I will try this solution, too. Thanks @C3C0.

davcec commented 7 years ago

There is no way to call super method via reflect.

:+1: Gotcha, thanks @liudongmiao.

Don't suppose you can always hook an app.

Sure I don't, but it's fun to try. ;-) Cheers.

C3C0 commented 7 years ago

I guess that param.getResult() will actually trigger a call to the original method. Instead of my suggested before hook you can also use this kind of replace hook. XposedHelpers.findAndHookMethod("Activity2$1", lpparam.classLoader, "run", XC_MethodReplacement.DO_NOTHING);

davcec commented 6 years ago

I see the hook is working: it's just the new activity slow to start. Thank all of you guys for the support. Cheers.

rovo89 commented 6 years ago

I guess that param.getResult() will actually trigger a call to the original method.

Actually no, it doesn't. It will simply return null (or, if you have previously called param.setResult(), it will return that result).

Using DO_NOTHING is still recommended though because it's self-explaining.

neoblackxt commented 5 years ago

After hours thinking, I find a way to bypass the SuperNotCalledException

findAndHookMethod("SomeActivity", classLoader, "onCreate", Bundle.class, new XC_MethodReplacement() {
        @Override
        protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
                Activity activity = (Activity) param.thisObject;
                findField(Activity.class,"mCalled").setAccessible(true);
                setBooleanField(activity,"mCalled",true);
                activity.setResult(Activity.RESULT_OK);
                activity.finish();
                param.setResult(null);
                return null;
        }

Here is why: android.app.ActivityThread.performLaunchActivity()

        activity.mCalled = false;
        mInstrumentation.callActivityOnCreate(activity, r.state);
        if (!activity.mCalled) {
                throw new SuperNotCalledException(
                        Activity " + r.intent.getComponent().toShortString() +
                                did not call through to super.onCreate()");
        }