Implement hook APIs
This commit is contained in:
parent
06c65a5a61
commit
c77617c3e1
|
|
@ -20,10 +20,20 @@
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||||
|
|
||||||
|
import java.lang.reflect.Executable;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Member;
|
import java.lang.reflect.Member;
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
import de.robv.android.xposed.callbacks.IXUnhook;
|
import de.robv.android.xposed.callbacks.IXUnhook;
|
||||||
import de.robv.android.xposed.callbacks.XCallback;
|
import de.robv.android.xposed.callbacks.XCallback;
|
||||||
|
import io.github.libxposed.XposedInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback class for method hooks.
|
* Callback class for method hooks.
|
||||||
|
|
@ -93,7 +103,7 @@ public abstract class XC_MethodHook extends XCallback {
|
||||||
/**
|
/**
|
||||||
* Wraps information about the method call and allows to influence it.
|
* Wraps information about the method call and allows to influence it.
|
||||||
*/
|
*/
|
||||||
public static final class MethodHookParam extends XCallback.Param {
|
public static final class MethodHookParam <T> extends XCallback.Param implements XposedInterface.BeforeHookCallback<T>, XposedInterface.AfterHookCallback<T> {
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
|
@ -121,6 +131,8 @@ public abstract class XC_MethodHook extends XCallback {
|
||||||
private Throwable throwable = null;
|
private Throwable throwable = null;
|
||||||
public boolean returnEarly = false;
|
public boolean returnEarly = false;
|
||||||
|
|
||||||
|
private final HashMap<String, Object> extras = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the result of the method call.
|
* Returns the result of the method call.
|
||||||
*/
|
*/
|
||||||
|
|
@ -146,6 +158,11 @@ public abstract class XC_MethodHook extends XCallback {
|
||||||
return throwable;
|
return throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSkipped() {
|
||||||
|
return returnEarly;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if an exception was thrown by the method.
|
* Returns true if an exception was thrown by the method.
|
||||||
*/
|
*/
|
||||||
|
|
@ -172,6 +189,51 @@ public abstract class XC_MethodHook extends XCallback {
|
||||||
throw throwable;
|
throw throwable;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public T getOrigin() {
|
||||||
|
return (T) method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object getThis() {
|
||||||
|
return thisObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Object[] getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void returnAndSkip(@Nullable Object returnValue) {
|
||||||
|
setResult(returnValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void throwAndSkip(@Nullable Throwable throwable) {
|
||||||
|
setThrowable(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object invokeOrigin(@Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
return HookBridge.invokeOriginalMethod((Executable) method, thisObject, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <U> U getExtra(@NonNull String key) {
|
||||||
|
return (U) extras.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> void setExtra(@NonNull String key, @Nullable U value) throws ConcurrentModificationException {
|
||||||
|
extras.put(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
import io.github.libxposed.XposedInterface;
|
import io.github.libxposed.XposedInterface;
|
||||||
|
import io.github.libxposed.XposedModuleInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class contains most of Xposed's central logic, such as initialization and callbacks used by
|
* This class contains most of Xposed's central logic, such as initialization and callbacks used by
|
||||||
|
|
@ -395,7 +396,7 @@ public final class XposedBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AdditionalHookInfo {
|
public static class AdditionalHookInfo<T> {
|
||||||
private final Object params;
|
private final Object params;
|
||||||
|
|
||||||
private AdditionalHookInfo(Executable method) {
|
private AdditionalHookInfo(Executable method) {
|
||||||
|
|
@ -416,7 +417,7 @@ public final class XposedBridge {
|
||||||
// This method is quite critical. We should try not to use system methods to avoid
|
// This method is quite critical. We should try not to use system methods to avoid
|
||||||
// endless recursive
|
// endless recursive
|
||||||
public Object callback(Object[] args) throws Throwable {
|
public Object callback(Object[] args) throws Throwable {
|
||||||
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
XC_MethodHook.MethodHookParam<T> param = new XC_MethodHook.MethodHookParam<>();
|
||||||
|
|
||||||
var array = ((Object[]) params);
|
var array = ((Object[]) params);
|
||||||
|
|
||||||
|
|
@ -451,7 +452,12 @@ public final class XposedBridge {
|
||||||
int beforeIdx = 0;
|
int beforeIdx = 0;
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
|
var cb = callbacksSnapshot[beforeIdx];
|
||||||
|
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
|
||||||
|
((XC_MethodHook) cb).beforeHookedMethod(param);
|
||||||
|
} else if (HookBridge.instanceOf(cb, XposedInterface.BeforeMethodHooker.class)) {
|
||||||
|
((XposedInterface.BeforeMethodHooker<T>) cb).before(param);
|
||||||
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
XposedBridge.log(t);
|
XposedBridge.log(t);
|
||||||
|
|
||||||
|
|
@ -483,8 +489,13 @@ public final class XposedBridge {
|
||||||
Object lastResult = param.getResult();
|
Object lastResult = param.getResult();
|
||||||
Throwable lastThrowable = param.getThrowable();
|
Throwable lastThrowable = param.getThrowable();
|
||||||
|
|
||||||
|
var cb = callbacksSnapshot[afterIdx];
|
||||||
try {
|
try {
|
||||||
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
|
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
|
||||||
|
((XC_MethodHook) cb).afterHookedMethod(param);
|
||||||
|
} else if (HookBridge.instanceOf(cb, XposedInterface.AfterHookCallback.class)) {
|
||||||
|
((XposedInterface.AfterMethodHooker<T>) cb).after(param);
|
||||||
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
XposedBridge.log(t);
|
XposedBridge.log(t);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package org.lsposed.lspd.impl;
|
package org.lsposed.lspd.impl;
|
||||||
|
|
||||||
|
import static de.robv.android.xposed.callbacks.XCallback.PRIORITY_DEFAULT;
|
||||||
|
|
||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
import android.app.LoadedApk;
|
import android.app.LoadedApk;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
|
|
@ -49,6 +51,7 @@ import java.io.FileOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
|
|
@ -56,6 +59,8 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XC_MethodHook;
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
import de.robv.android.xposed.XposedInit;
|
import de.robv.android.xposed.XposedInit;
|
||||||
import io.github.libxposed.XposedContext;
|
import io.github.libxposed.XposedContext;
|
||||||
|
|
@ -754,76 +759,102 @@ public class LSPosedContext extends XposedContext {
|
||||||
return BuildConfig.VERSION_CODE;
|
return BuildConfig.VERSION_CODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
private <T, U extends Executable> MethodUnhooker<T, U> doHook(U hookMethod, int priority, T callback) {
|
||||||
|
if (Modifier.isAbstract(hookMethod.getModifiers())) {
|
||||||
|
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod);
|
||||||
|
} else if (hookMethod.getDeclaringClass().getClassLoader() == XposedBridge.class.getClassLoader()) {
|
||||||
|
throw new IllegalArgumentException("Do not allow hooking inner methods");
|
||||||
|
} else if (hookMethod.getDeclaringClass() == Method.class && hookMethod.getName().equals("invoke")) {
|
||||||
|
throw new IllegalArgumentException("Cannot hook Method.invoke");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback == null) {
|
||||||
|
throw new IllegalArgumentException("callback should not be null!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HookBridge.hookMethod(hookMethod, XposedBridge.AdditionalHookInfo.class, priority, callback)) {
|
||||||
|
return new MethodUnhooker<>() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public U getOrigin() {
|
||||||
|
return hookMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public T getHooker() {
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unhook() {
|
||||||
|
HookBridge.unhookMethod(hookMethod, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
log("Cannot hook " + hookMethod);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, @NonNull BeforeMethodHooker<Method> hooker) {
|
public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, @NonNull BeforeMethodHooker<Method> hooker) {
|
||||||
return null;
|
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, @NonNull AfterMethodHooker<Method> hooker) {
|
public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, @NonNull AfterMethodHooker<Method> hooker) {
|
||||||
return null;
|
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, @NonNull MethodHooker<Method> hooker) {
|
public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, @NonNull MethodHooker<Method> hooker) {
|
||||||
return null;
|
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, int priority, @NonNull BeforeMethodHooker<Method> hooker) {
|
public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, int priority, @NonNull BeforeMethodHooker<Method> hooker) {
|
||||||
return null;
|
return doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, int priority, @NonNull AfterMethodHooker<Method> hooker) {
|
public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, int priority, @NonNull AfterMethodHooker<Method> hooker) {
|
||||||
return null;
|
return doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, int priority, @NonNull MethodHooker<Method> hooker) {
|
public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, int priority, @NonNull MethodHooker<Method> hooker) {
|
||||||
return null;
|
return doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, @NonNull BeforeMethodHooker<Constructor<T>> hooker) {
|
public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, @NonNull BeforeMethodHooker<Constructor<T>> hooker) {
|
||||||
return null;
|
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, @NonNull AfterMethodHooker<Constructor<T>> hooker) {
|
public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, @NonNull AfterMethodHooker<Constructor<T>> hooker) {
|
||||||
return null;
|
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull MethodHooker<Constructor<T>> hooker) {
|
public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull MethodHooker<Constructor<T>> hooker) {
|
||||||
return null;
|
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@Override
|
@Override
|
||||||
public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, int priority, @NonNull BeforeMethodHooker<Constructor<T>> hooker) {
|
public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, int priority, @NonNull BeforeMethodHooker<Constructor<T>> hooker) {
|
||||||
return null;
|
return doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, int priority, @NonNull AfterMethodHooker<Constructor<T>> hooker) {
|
public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, int priority, @NonNull AfterMethodHooker<Constructor<T>> hooker) {
|
||||||
return null;
|
return doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Override
|
@Override
|
||||||
public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull MethodHooker<Constructor<T>> hooker) {
|
public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull MethodHooker<Constructor<T>> hooker) {
|
||||||
return null;
|
return doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean deoptimize(@NonNull Executable method) {
|
private static boolean deoptimize(@NonNull Executable method) {
|
||||||
|
|
@ -845,11 +876,10 @@ public class LSPosedContext extends XposedContext {
|
||||||
return deoptimize((Executable) constructor);
|
return deoptimize((Executable) constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public XposedUtils getUtils() {
|
public XposedUtils getUtils() {
|
||||||
return null;
|
return new LSPosedUtils(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.lsposed.lspd.impl;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import io.github.libxposed.XposedUtils;
|
||||||
|
|
||||||
|
public class LSPosedUtils implements XposedUtils {
|
||||||
|
private final LSPosedContext context;
|
||||||
|
|
||||||
|
LSPosedUtils(LSPosedContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <T> Class<T> classByName(@NonNull String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
|
||||||
|
throw new AbstractMethodError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T> Class<T> classOrNullByName(@NonNull String className, @Nullable ClassLoader classLoader) {
|
||||||
|
throw new AbstractMethodError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <T> Class<T> classByBinaryName(@NonNull String binaryClassName, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
|
||||||
|
throw new AbstractMethodError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T> Class<T> classOrNullByBinaryName(@NonNull String binaryClassName, @Nullable ClassLoader classLoader) {
|
||||||
|
throw new AbstractMethodError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ConcurrentModificationException;
|
import java.util.ConcurrentModificationException;
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ public interface XposedInterface {
|
||||||
void throwAndSkip(@Nullable Throwable throwable);
|
void throwAndSkip(@Nullable Throwable throwable);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Object invokeOrigin(@Nullable Object thisObject, Object[] args);
|
Object invokeOrigin(@Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException;
|
||||||
|
|
||||||
<U> void setExtra(@NonNull String key, @Nullable U value) throws ConcurrentModificationException;
|
<U> void setExtra(@NonNull String key, @Nullable U value) throws ConcurrentModificationException;
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +54,7 @@ public interface XposedInterface {
|
||||||
void setThrowable(@Nullable Throwable throwable);
|
void setThrowable(@Nullable Throwable throwable);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Object invokeOrigin(@Nullable Object thisObject, Object[] args);
|
Object invokeOrigin(@Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
<U> U getExtra(@NonNull String key);
|
<U> U getExtra(@NonNull String key);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue