这里说的multidex是指apk包含多个dex。实现这个的技术,除了使用support提供的multidex库,也可以自己开发,实现逻辑不复杂。同时,最新安卓系统已支持自动加载或转换多个dex。在不考虑兼容旧版情况下,甚至可以不用手动加载。当然,若dex需要经过解密后才能加载的话,就得自己实现这个解密和加载过程。
一般我们修改别人的dex,会先转成jar或smali,然后对里面内容进行修改(字节码操作或源码操作),最后重新编译成dex。 但是,这种修改方案有一个不好的地方,就是直接改变了别人代码(我还是很介意代码倾入逻辑,即使是改软件)。嘛,若只是修改一两个dex,这种方案还是可以。但是,若dex转换失败或存在错误(安卓支持的指令并不完全跟java一样,需要兼容处理),重新编译势必存在问题。同时,转换后的代码,源码操作需要再进行转换,失败概率更高(语法很多是错的)。字节码操作好点,但是阅读困难。所以,一些简单的调整,我会选择字节码操作,很少转成源码后再修改。而复杂的调整,基本不会选择这种修改方案,除非有必要,不得不深入分析。
multidex除了把应用代码分包外,同时也用于局部功能升级。 从其技术上说,是利用了虚拟机动态类加载的机制。换句话说,虚拟机在没有使用某个类时,是不会加载相关库。这时,我们就可以在虚拟机加载前,替换这些库,实现代码更新。对于已经加载的类,可以利用加载器的查询逻辑,进行替换,从而实现热更新的目的。我们经常使用的调试就是属于这种热更新。
多个库出现相同类不会有冲突吗?我们在编译源码时,若库出现重复类,打包会失败,这是dx转换问题。但是,对于虚拟机而言,加载的库有没有重复类不是它关心的问题,也不会出现问题(除非库不兼容),它只关心这个库里面是否有需要的类。 这个类一旦加载,在不卸载库的情况下,其它库包含同样的类都不会被使用,即采用先到先用原则。
基于以上multidex的说明,我们可以知道,若我们想要改变已有dex的功能,可以优先加载修改过后的类,继而实现覆盖原有类,替换其功能逻辑。 相比直接修改dex,改变dex加载顺序,同时开发替换的类更加简单。
实现流程:
先开发覆盖原有类的功能代码(这个根据实际需要开发,只要明确覆盖哪个类,替换什么功能就行)。然后,重写ApplicationContext,让其实现multidex,并在加载后,启动原有的ApplicationContext。对于动态替换ApplicationContext和启动其它ApplicationContext的方案,网上有介绍,大家可以搜索看看。但是大部分情况下,我们不用完全替换。ApplicationContext的逻辑一般很简单,编写一个wrapper(开放接口尽量跟对方一致,防止对方写了强制转换的逻辑)就可以兼容全部场景,除非对方的ApplicationContext写得比较特殊,这时才需要完全替换。若修改的应用或系统支持multidex,除非有必要重写ApplicationContext(有些框架初始会放在这里,若需要修改这些,就得替换),不然只要添加自己修改后的dex就行(记得明确自己的dex优先加载,否则必须修改ApplicationContext,改变加载顺序)。
这种方案也是目前加固产品的一种实现逻辑。首先,加固代码优先加载,然后根据加密规则,恢复对方dex,然后加载这些dex,最后启动对方的ApplicationContext。
我经常用这种方案拦截一些框架的日志输出、统计输出、调整一些无法直接处理的问题(别人的东西有时真的不适合自己的需要,不得不调整,但是又不能、不想操作对方代码,这时就可以重新编写这些类)。以下是我做的一些调整例子:
替换eventbus日志输出(输出到自己的日志工具):
package org.greenrobot.eventbus;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;
import android.os.Looper;
import android.util.Log;
public class EventBusBuilder
{
private static final ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
boolean eventInheritance = true;
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
boolean ignoreGeneratedIndex;
boolean logNoSubscriberMessages = true;
boolean logSubscriberExceptions = true;
Logger logger;
MainThreadSupport mainThreadSupport;
boolean sendNoSubscriberEvent = true;
boolean sendSubscriberExceptionEvent = true;
boolean strictMethodVerification;
List<SubscriberInfoIndex> subscriberInfoIndexes;
boolean throwSubscriberException;
Object getAndroidMainLooperOrNull()
{
Looper localLooper = Looper.getMainLooper();
return localLooper;
}
Logger getLogger()
{
if (this.logger != null) return this.logger;
return new Logger() {
String tag = "EventBus";
@Override
public void log(Level arg0, String arg1) {
org.ufo.util.Logger.t(this.tag, arg1);
}
@Override
public void log(Level arg0, String arg1, Throwable arg2) {
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append(arg1);
localStringBuilder.append("\n");
localStringBuilder.append(Log.getStackTraceString(arg2));
org.ufo.util.Logger.t(this.tag, localStringBuilder.toString());
}
};
}
MainThreadSupport getMainThreadSupport()
{
if (this.mainThreadSupport != null) return this.mainThreadSupport;
if (Logger.AndroidLogger.isAndroidLogAvailable())
{
Object localObject = getAndroidMainLooperOrNull();
if (localObject == null) return null;
return new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) localObject);
}
return null;
}
}
替换okhttp默认事件输出(替换NONE就行,用户在没有配置事件输出时,会默认使用这个对象):
package okhttp3;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
public class EventListener
{
public static final EventListener NONE = new EventListener();
public static final LogEventListener LOG = new LogEventListener();
static Factory factory(final EventListener paramEventListener)
{
return new Factory()
{
public EventListener create(Call paramCall)
{
return paramEventListener;
}
};
}
public void callEnd(Call paramCall)
{
}
public void callFailed(Call paramCall, IOException paramIOException)
{
}
public void callStart(Call paramCall)
{
}
public void connectEnd(Call paramCall, InetSocketAddress paramInetSocketAddress, Proxy paramProxy, Protocol paramProtocol)
{
}
public void connectFailed(Call paramCall, InetSocketAddress paramInetSocketAddress, Proxy paramProxy, Protocol paramProtocol, IOException paramIOException)
{
}
public void connectStart(Call paramCall, InetSocketAddress paramInetSocketAddress, Proxy paramProxy)
{
}
public void connectionAcquired(Call paramCall, Connection paramConnection)
{
}
public void connectionReleased(Call paramCall, Connection paramConnection)
{
}
public void dnsEnd(Call paramCall, String paramString, List<InetAddress> paramList)
{
}
public void dnsStart(Call paramCall, String paramString)
{
}
public void requestBodyEnd(Call paramCall, long paramLong)
{
}
public void requestBodyStart(Call paramCall)
{
}
public void requestHeadersEnd(Call paramCall, Request paramRequest)
{
}
public void requestHeadersStart(Call paramCall)
{
}
public void responseBodyEnd(Call paramCall, long paramLong)
{
}
public void responseBodyStart(Call paramCall)
{
}
public void responseHeadersEnd(Call paramCall, Response paramResponse)
{
}
public void responseHeadersStart(Call paramCall)
{
}
public void secureConnectEnd(Call paramCall, Handshake paramHandshake)
{
}
public void secureConnectStart(Call paramCall)
{
}
public static abstract interface Factory
{
public abstract EventListener create(Call paramCall);
}
}
替换ReactNative默认Printer(默认printer会通过这个类获取,替换这个类,返回自己的实现即可):
package com.facebook.debug.holder;
public class PrinterHolder
{
public static Printer getPrinter()
{
return DebugPrinter.INSTANCE;
}
}
替换ReactNative的日志输出:
package com.facebook.common.logging;
import org.ufo.util.Logger;
public class FLog
{
public static boolean show = false;
public static void d(Class<?> paramClass, String paramString, Object paramObject)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, new Object[] { paramObject }));
}
public static void d(String paramString1, String paramString2)
{
if (show) Logger.t(paramString1, paramString2);
}
public static void e(Class<?> paramClass, String paramString)
{
if (show) Logger.t(getTag(paramClass), paramString);
}
public static void e(Class<?> paramClass, String paramString, Throwable paramThrowable)
{
if (show) Logger.t(getTag(paramClass), paramString, paramThrowable);
}
public static void e(Class<?> paramClass, String paramString, Object[] paramArrayOfObject)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, paramArrayOfObject));
}
public static void e(Class<?> paramClass, Throwable paramThrowable, String paramString, Object[] paramArrayOfObject)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, paramArrayOfObject), paramThrowable);
}
public static void e(String paramString1, String paramString2)
{
if (show) Logger.t(paramString1, paramString2);
}
public static void e(String paramString1, String paramString2, Throwable paramThrowable)
{
if (show) Logger.t(paramString1, paramString2, paramThrowable);
}
public static void e(String paramString1, Throwable paramThrowable, String paramString2, Object[] paramArrayOfObject)
{
if (show) Logger.t(paramString1, formatString(paramString2, paramArrayOfObject), paramThrowable);
}
private static String formatString(String paramString, Object[] paramArrayOfObject)
{
return String.format(null, paramString, paramArrayOfObject);
}
private static String getTag(Class<?> paramClass)
{
return paramClass.getSimpleName();
}
public static void i(Class<?> paramClass, String paramString)
{
if (show) Logger.t(getTag(paramClass), paramString);
}
public static void i(String paramString1, String paramString2)
{
if (show) Logger.t(paramString1, paramString2);
}
public static boolean isLoggable(int paramInt)
{
return true;
}
public static void v(Class<?> paramClass, String paramString)
{
if (show) Logger.t(getTag(paramClass), paramString);
}
public static void v(Class<?> paramClass, String paramString, Object paramObject)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, new Object[] { paramObject }));
}
public static void v(Class<?> paramClass, String paramString, Object paramObject1, Object paramObject2)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, new Object[] { paramObject1, paramObject2 }));
}
public static void v(Class<?> paramClass, String paramString, Object paramObject1, Object paramObject2, Object paramObject3)
{
v(paramClass, formatString(paramString, new Object[] { paramObject1, paramObject2, paramObject3 }));
}
public static void v(Class<?> paramClass, String paramString, Object paramObject1, Object paramObject2, Object paramObject3, Object paramObject4)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, new Object[] { paramObject1, paramObject2, paramObject3, paramObject4 }));
}
public static void v(Class<?> paramClass, String paramString, Object[] paramArrayOfObject)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, paramArrayOfObject));
}
public static void v(String paramString1, String paramString2, Object[] paramArrayOfObject)
{
if (show) Logger.t(paramString1, formatString(paramString2, paramArrayOfObject));
}
public static void w(Class<?> paramClass, String paramString)
{
if (show) Logger.t(getTag(paramClass), paramString);
}
public static void w(Class<?> paramClass, String paramString, Throwable paramThrowable)
{
if (show) Logger.t(getTag(paramClass), paramString, paramThrowable);
}
public static void w(Class<?> paramClass, String paramString, Object[] paramArrayOfObject)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, paramArrayOfObject));
}
public static void w(Class<?> paramClass, Throwable paramThrowable, String paramString, Object[] paramArrayOfObject)
{
w(paramClass, formatString(paramString, paramArrayOfObject), paramThrowable);
}
public static void w(String paramString1, String paramString2)
{
if (show) Logger.t(paramString1, paramString2);
}
public static void w(String paramString1, String paramString2, Throwable paramThrowable)
{
if (show) Logger.t(paramString1, paramString2, paramThrowable);
}
public static void w(String paramString1, String paramString2, Object[] paramArrayOfObject)
{
if (show) Logger.t(paramString1, formatString(paramString2, paramArrayOfObject));
}
public static void w(String paramString1, Throwable paramThrowable, String paramString2, Object[] paramArrayOfObject)
{
if (show) Logger.t(paramString1, formatString(paramString2, paramArrayOfObject), paramThrowable);
}
public static void wtf(Class<?> paramClass, String paramString, Throwable paramThrowable)
{
if (show) Logger.t(getTag(paramClass), paramString, paramThrowable);
}
public static void wtf(Class<?> paramClass, String paramString, Object[] paramArrayOfObject)
{
if (show) Logger.t(getTag(paramClass), formatString(paramString, paramArrayOfObject));
}
public static void wtf(String paramString1, String paramString2, Object[] paramArrayOfObject)
{
if (show) Logger.t(paramString1, formatString(paramString2, paramArrayOfObject));
}
}