這次準備寫一系列的關於自己學習外掛化的過程了,前面雖然陸陸續續的學習了一些外掛化方面的知識,但是都是淡淡續續的,這次要從基礎開始了,其實我一直認為基礎這個東西挺重要的。
本文程式碼在我的Github上面:Plugin Demo
這裡首先說明一下代理模式在外掛化中的作用,代理其實就是劫持一個類,然後在執行指定的方法的時候,做一些事,一些什麼事情呢,比如在前面或者後面加個統計,或者直接替換原來的執行邏輯,換成自己的邏輯。
其他比如,我們需要在不同的情況下返回同一個方法的不同實現也可以使用代理模式的,比如在做Push的時候,我們可能在App裡面不止使用了一個推送服務,比如很多App會同時整合,個推和小米以及華為的推送,那麼我們需要在不同的手機上面使用不同的推送,小米和和華為手機上面使用他們自己的,其餘的手機上面使用個推的。那這個時候就可以使用代理模式來實現,因為推送基本就那幾個方法,一是開啟推送,一是給裝置打標籤等等方法需要我們實現。那分別用幾個實現類實現即可,好吧,其實這個是策略模式,逃。
我們時常會看到別人說hook,特別是在外掛化的時候,hook住Activity的啟動,通過預先埋在Manifest裡面的Activity,替換成我們真正想要啟動的Activity等等的說法。通常Hook,又叫鉤子,通常是指對一些方法進行攔截。這樣當這些方法被呼叫時,也能夠執行我們自己的程式碼,這也是面向切面程式設計的思想(AOP)。 Android中,本身並不提供這樣的攔截機制,但是有時候,我們可以在一些特殊的場合實現一種的Hook方法。 大致思路: 1.找到需要Hook方法的系統類 2.利用代理模式來代理系統類的執行攔截我們需要攔截的方法 3.使用反射的方法把這個系統類替換成你的代理類 上面的一段話取自外掛化知識詳細分解及原理 之代理,hook,反射,感覺說的挺好。這裡是不是就能感受到代理模式的強大了,下面會細說的。
代理模式的意思就是為其他物件提供一種代理以控制對這個物件的訪問,一般當我們無法或者不想直接訪問某個物件或者訪問某個物件存在困難時,可以用過一個代理物件來間接訪問。(下圖出自:代理模式及Java實現動態代理)
java中的代理模式大概可以分為兩種,一種就是普通的代理也就是靜態代理,就是我們生成固定的程式碼,在我們執行前代理類的class編譯檔案就已經存在啦,動態代理與靜態代理相反,在code階段壓根不需要知道代理誰,代理誰將會在程式碼的執行階段通過一些判斷來決定代理哪個物件。動態代理其實如果細分也可以分成兩類,一類是JDK提供的代理,一類是cglib提供的代理類,他們的區別是: JDK動態代理只能對實現了介面的類生成代理,而不能針對類 。 CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法 。 因為是繼承,所以該類或方法最好不要宣告成final ,final可以阻止繼承和多型。這裡需要注意,因為是代理模式,肯定是需要真正代理某個類的,也就是說需要真正做事得類,然後對這個類進行代理。這點是基礎,不然說不定你會越看越懵逼。
普通代理
假設這樣一個例子,小明要做某件事(自行腦補什麼事),他不想自己做,想委託給別人做,那麼這個代理怎麼寫呢。 按照套路我們首先需要定義一個介面,裡面有一個做事情的方法。
public interface IDoThing {
void doSomeThing();
}
複製程式碼
然後需要真正做事的類,小明,就像上面說的,這個是基礎,這個都沒有,代理個毛。
public class Xiaoming implements IDoThing {
@Override
public void doSomeThing() {
System.out.println("xiaoming doSomething()");
}
}
複製程式碼
然後是代理類的實現:
public class ProxyXiaoming implements IDoThing {
private IDoThing mSubject;
public ProxyXiaoming(IDoThing subject) {
mSubject = subject;
}
@Override
public void doSomeThing() {
mSubject.doSomeThing();
}
}
複製程式碼
最後是呼叫的時候啦:
public class ProxyMain {
public static void main(String[] params) {
Xiaoming real = new Xiaoming();
// 這個代理了xiaoming,如果想要再代理別人,需要重新建立ProxyXiaohuang等等
ProxyXiaoming proxy = new ProxyXiaoming(real);
// 要訪問Xiaoming的doSomeThing()通過ProxyXiaoming去訪問
proxy.doSomeThing();
}
}
複製程式碼
JDK動態代理
先說實現,然後細說一下。
public class InvocationProxy implements InvocationHandler {
private Object target;
/**
* 繫結委託物件並返回一個代理類
*
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//取得代理物件
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this); //要繫結介面(這是一個缺陷,cglib彌補了這一缺陷)
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (target == null) return null;
Object result;
System.out.println("事物開始");
//執行方法
result = method.invoke(target, args);
System.out.println("事物結束");
return result;
}
}
複製程式碼
public class InvocationMain {
public static void main(String[] params) {
// 動態代理,具體的每個真實的類也要先弄好,只是正呼叫的時候可以根據不同的條件呼叫不同的方法
// 或者在呼叫那個方法的前後做一些事情
// InvocationHandler只能代理介面
final Xiaoming real = new Xiaoming();
// 代理Xiaoming
IDoThing doThing = (IDoThing) Proxy.newProxyInstance(
real.getClass().getClassLoader(),
new Class[]{IDoThing.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = method.invoke(real, args);
return object;
}
});
doThing.doSomeThing();
// 另一寫法
// InvocationProxy proxy = new InvocationProxy();
// IDoThing iDoThing = (IDoThing) proxy.bind(new Xiaoming());
// iDoThing.doSomeThing();
}
}
複製程式碼
JDK實現動態代理主要涉及以下幾個類:
java.lang.reflect.Proxy
:這是生成代理類的主類,通過 Proxy 類生成的代理類都繼承了 Proxy 類。
java.lang.reflect.InvocationHandler
: 這裡稱他為"呼叫處理器",簡單說這個類就是,對我們需要的方法進行處理的,invoke
方法會主動呼叫,我們需要的是處理它的內部實現。
cglib動態代理
public class CglibProxy implements MethodInterceptor {
private Object target;
/**
* 建立代理物件
*
* @param target
* @return
*/
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回撥方法
enhancer.setCallback(this);
// 建立代理物件
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (target == null) return null;
Object result;
System.out.println("事物開始");
//執行方法
result = methodProxy.invokeSuper(o, args);
System.out.println("事物結束");
return result;
}
}
複製程式碼
public class CglibMain {
public static void main(String[] params) {
CglibProxy proxy = new CglibProxy();
Xiaoming xiaoming = (Xiaoming) proxy.getInstance(new Xiaoming());
xiaoming.doSomeThing();
}
}
複製程式碼
是不是和jdk的動態代理差不多的。先建立代理物件,然後攔截方法的。
看到上面寫了一堆是不是感覺沒什麼用,是吧,這樣想就對啦,這裡列出一個實際的例子,比如我們有這樣一個需求,需要在一定的範圍內禁止掉List
的add
方法:
/**
* 禁止List的add功能
*
* @param list
* @return
*/
public static List getList(final List list) {
return (List) Proxy.newProxyInstance(list.getClass().getClassLoader(),
new Class[]{List.class},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("add".equals(method.getName())) {
throw new UnsupportedOperationException();
} else {
return method.invoke(list, args);
}
}
});
}
複製程式碼
哈哈,是不是很奇妙的。
Hook Instrumentation建立Activity的方法
我在Android的資源管理器的建立過程裡面寫到過,啟動Activity即Activity的建立過程了的。
其實一般的時候我們Hook,需要找對點的,什麼叫Hook點呢。 這是weishu大神說過的。 這裡,實際上使用了ActivityThread類的mInstrumentation成員的execStartActivity方法;注意到,ActivityThread 實際上是主執行緒,而主執行緒一個程式只有一個,因此這裡是一個良好的Hook點。其實一個App的主入口就是ActivityThread,它裡面有main方法的。 所以我們可以拿到mMainThread然後修改掉它的mInstrumentation物件為我們的代理物件。 實現如下:public class HookHelper {
/**
* Context的startActivity最終是由ContextImpl實現的,
* 呼叫ActivityThread成員的Instrumentation物件的execStartActivity方法
*/
public static void hookContextStartActivity() throws Exception {
// 先獲取到當前的ActivityThread物件
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
// 找到currentActivityThread靜態函式
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
//currentActivityThread是一個static函式所以可以直接invoke,不需要帶例項引數,直接拿到的就是ActivityThread物件
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 拿到原始的 mInstrumentation欄位
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
// 拿到Instrumentation物件從currentActivityThread裡面
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
// 新建增加版本Instrumentation例項
EvilInstrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
// 設定成增強版本的Instrumentation
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}
}
複製程式碼
public class EvilInstrumentation extends Instrumentation {
private static final String TAG = EvilInstrumentation.class.getSimpleName();
// ActivityThread中原始的物件, 儲存起來
Instrumentation mBase;
public EvilInstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(
Context who,
IBinder contextThread,
IBinder token,
Activity target,
Intent intent,
int requestCode,
Bundle options) {
// Hook之前, 做處理
Log.e(TAG, "\n執行了startActivity, 引數如下: \n" + "who = [" + who + "], " +
"\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
"\ntarget = [" + target + "], \nintent = [" + intent +
"], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
// 開始呼叫原始的方法, 調不呼叫隨你,但是不呼叫的話, 所有的startActivity都失效了.
// 由於這個方法是隱藏的,因此需要使用反射呼叫;首先找到這個方法
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class,
IBinder.class,
IBinder.class,
Activity.class,
Intent.class,
int.class,
Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(
mBase,
who,
contextThread,
token,
target,
intent,
requestCode,
options);
} catch (Exception e) {
// 某該死的rom修改了 需要手動適配
throw new RuntimeException("do not support pls adapt it");
}
}
}
複製程式碼
以上實現是weishu大神寫的,我只是挪用。 呼叫,由於這裡只是舉例,真正的外掛化不會這麼寫的。
public class MainActivity extends BaseActivity {
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
HookHelper.hookContextStartActivity();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void contextStartAct(View view) {
Application application = getApplication();
Intent intent = new Intent(this, LaunchedActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
application.startActivity(intent);
}
public void activityStartAct(View view) {
Intent intent = new Intent(this, LaunchedActivity.class);
startActivity(intent);
}
}
複製程式碼
這裡只是Hook了Context的startActivity,Activity是自己實現了startActivity的。
系統中的代理模式的使用
安卓系統中用到代理模式還是挺多的,與Binder相關的都是用到了代理模式的,關於Binder後面再說,我們平時寫的AIDL都是用了代理模式的,一般在使用AIDL的時候,如果不需要跨程式就返回Binder本地物件,如果需要就返回代理物件。
這裡先說一一個具體的代理的例子,然後說一下Binder中的代理,順便說一下Binder。
這個例子是ActivityManagerProxy的代理實現,它代理了ActivityManagerService這個類,然後在呼叫使用IActivityManager裡面的方法的時候,因為真正的實現是ActivityManagerService,這個是執行在系統的程式中的,我們要呼叫的話就要跨程式呼叫了,安卓跨程式這裡使用的是Binder。
Binder的一般套路是這樣,因為跨程式,所以是分為客戶端和服務端的,客戶端就是執行在客戶端程式中的,服務端就是執行在另外的程式,可能是系統的,可能是別人提供的某種服務(比如Google Play的支付就是使用),由於需要提供服務給使用,所以一般會定義一個介面,然後這個介面需要繼承IInterface
這個介面,這個介面的解釋是這樣的:
/**
* Base class for Binder interfaces. When defining a new interface,
* you must derive it from IInterface.
*/
複製程式碼
就是說如果想用Binder實現跨程式,那麼必須要繼承這個類。
然後再說客戶端和服務端,其實客戶端挺簡單。
客戶端只需要實現對應的介面,這裡是IActivityManager
,然後實現裡面的方法即可,那其實為什麼需要跨程式,其實就是我們需要呼叫的方法在服務端裡面,那這裡我們實現這些方法要寫什麼呢,怎麼寫呢,其實也是有套路的。
public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(receiver.asBinder());
mRemote.transact(UNREGISTER_RECEIVER_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
複製程式碼
隨便找一個方法分析,其實就是構造一些引數,包括傳遞過去的和返回來的,然後使用mRemote
這個物件去傳遞引數給服務端。所以在寫AIDL的時候IDE可以幫我們自動生成程式碼,因為介面寫完了的話,裡面每個函式的實現套路都是一樣的。
服務端就麻煩一下,服務端需要繼承Binder
這個類並且實現介面,這裡是IActivityManager
,然後這裡面就需要真的去實現IActivityManager
介面裡面想要做的事情了,這裡需要注意下面的方法。
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
複製程式碼
這個方法即是前面客戶端呼叫真正會呼叫到的服務端的方法,服務端先呼叫到這裡,然後填入引數,然後才會去呼叫IActivityManager
裡面的具體實現的方法,如果有返回值,會寫到reply
裡面的。
Binder裡面套路差不多都是這樣的,左邊圈起來的是客戶端,右邊是服務端,服務端裡面有真正的實現。服務端也叫Binder本地物件,我們寫AIDL的時候,如果不需要跨程式的話,返回的是Binder本地的物件,就是服務端的,因為他裡面有真正的實現的方法。不過一般在自己寫AIDL的時候,服務端的真正的需要使用的方法還是我們自己實現的,反而客戶端是代理物件,裡面雖然也實現了介面裡面一樣的方法,不過這些方法都是一些跨程式的操作,傳遞物件到服務端,等待服務端返回值。
系統中的Binder跨程式通訊
然後說說系統中的Binder跨程式通訊吧。framework層Binder類圖如下:
圖片取自Gityuan部落格. 關於這幾個類的解釋看他的部落格就行了,這裡就不抄襲了。 在Android系統開機過程中,Zygote啟動startReg會去註冊一系列的方法,從而把Java層的方法和JNI方法繫結在一起的。 例如: ===> AndroidRuntime.cpp---> register_jni_procs/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
ATRACE_NAME("RegisterAndroidNatives");
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
env->PushLocalFrame(200);
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
return 0;
}
複製程式碼
註冊JNI方法,其中gRegJNI是一個陣列,記錄所有需要註冊的jni方法,其中有一項便是REG_JNI(register_android_os_Binder)。
===> android_util_Binder.cpp--->register_android_os_Binder
int register_android_os_Binder(JNIEnv* env)
{
if (int_register_android_os_Binder(env) < 0)
return -1;
if (int_register_android_os_BinderInternal(env) < 0)
return -1;
if (int_register_android_os_BinderProxy(env) < 0)
return -1;
...
return 0;
}
複製程式碼
分別註冊Binder
,BinderInternal
,BinderProxy
這三個java類對應的在JNI中的實現。建立了是java類在Native層與framework層之間的相互呼叫的橋樑。
註冊服務: ===>ServiceManager.java
public static void addService(String name, IBinder service) {
try {
getIServiceManager().addService(name, service, false);
} catch (RemoteException e) {
}
}
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}
複製程式碼
這裡的就是剛上面說的,會註冊java方法和JNI方法的對映。也就是會呼叫android_util_Binder.cpp
裡的android_os_BinderInternal_getContextObject
方法。
====>android_util_Binder.cpp
static const JNINativeMethod gBinderInternalMethods[] = {
/* name, signature, funcPtr */
{ "getContextObject", "()Landroid/os/IBinder;", (void*)android_os_BinderInternal_getContextObject },
{ "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool },
{ "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling },
{ "setMaxThreads", "(I)V", (void*)android_os_BinderInternal_setMaxThreads },
{ "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }
};
複製程式碼
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
return javaObjectForIBinder(env, b);
}
複製程式碼
===>ProcessState.cpp
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
return getStrongProxyForHandle(0);
}
複製程式碼
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;
AutoMutex _l(mLock);
handle_entry* e = lookupHandleLocked(handle);
if (e != NULL) {
IBinder* b = e->binder;
if (b == NULL || !e->refs->attemptIncWeak(this)) {
if (handle == 0) {
Parcel data;
status_t status = IPCThreadState::self()->transact(
0, IBinder::PING_TRANSACTION, data, NULL, 0);
if (status == DEAD_OBJECT)
return NULL;
}
b = new BpBinder(handle);
e->binder = b;
if (b) e->refs = b->getWeakRefs();
result = b;
} else {
result.force_set(b);
e->refs->decWeak(this);
}
}
return result;
}
複製程式碼
ProcessState::self()->getContextObject()等價於 new BpBinder(0)。對於javaObjectForIBinder
這個方法。
====>android_util_Binder.cpp
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
if (val == NULL) return NULL;
if (val->checkSubclass(&gBinderOffsets)) {
// One of our own!
jobject object = static_cast<JavaBBinder*>(val.get())->object();
LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
return object;
}
// For the rest of the function we will hold this lock, to serialize
// looking/creation/destruction of Java proxies for native Binder proxies.
AutoMutex _l(mProxyLock);
// Someone else's... do we know about it?
jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
if (object != NULL) {
jobject res = jniGetReferent(env, object);
if (res != NULL) {
ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res);
return res;
}
LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get());
android_atomic_dec(&gNumProxyRefs);
val->detachObject(&gBinderProxyOffsets);
env->DeleteGlobalRef(object);
}
object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
if (object != NULL) {
LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object);
// The proxy holds a reference to the native object.
env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
val->incStrong((void*)javaObjectForIBinder);
// The native object needs to hold a weak reference back to the
// proxy, so we can retrieve the same proxy if it is still active.
jobject refObject = env->NewGlobalRef(
env->GetObjectField(object, gBinderProxyOffsets.mSelf));
val->attachObject(&gBinderProxyOffsets, refObject,
jnienv_to_javavm(env), proxy_cleanup);
// Also remember the death recipients registered on this proxy
sp<DeathRecipientList> drl = new DeathRecipientList;
drl->incStrong((void*)javaObjectForIBinder);
env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast<jlong>(drl.get()));
// Note that a new object reference has been created.
android_atomic_inc(&gNumProxyRefs);
incRefsCreated(env);
}
return object;
}
複製程式碼
根據BpBinder(C++)生成BinderProxy(Java)物件. 主要工作是建立BinderProxy物件,並把BpBinder物件地址儲存到BinderProxy.mObject成員變數. 到此,可知ServiceManagerNative.asInterface(BinderInternal.getContextObject()) 等價於:
ServiceManagerNative.asInterface(new BinderProxy())
複製程式碼
ServiceManagerNative.asInterface這個方法如下:
static public IServiceManager asInterface(IBinder obj)
{
if (obj == null) { //obj為BpBinder
return null;
}
//由於obj為BinderProxy,該方法預設返回null
IServiceManager in = (IServiceManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ServiceManagerProxy(obj);
}
複製程式碼
final class BinderProxy implements IBinder {
public IInterface queryLocalInterface(String descriptor) {
return null;
}
}
複製程式碼
由此,可知ServiceManagerNative.asInterface(new BinderProxy())
等價於new ServiceManagerProxy(new BinderProxy())
.
===> ServiceManagerProxy--->addService
public void addService(String name, IBinder service, boolean allowIsolated)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
data.writeStrongBinder(service);
data.writeInt(allowIsolated ? 1 : 0);
mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
reply.recycle();
data.recycle();
}
複製程式碼
前面已經說了mRemote是BinderProxy了。 ===>BinderProxy---> transactNative
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
複製程式碼
Java層的BinderProxy.transact()最終交由Native層的BpBinder::transact()完成。