幾年前作過一個需求,背景是這樣的:android
彼時採用的方案是對廣點通的jar包進行動態加載,即APP內不嵌入廣告SDK,在APP打開後,從服務器下載SDK到本地,使用類加載器去加載本地的SDK,經過反射機制建立廣告對象實例,完成加載須要。 當時作這些操做是爲了完成需求,並無對相關邏輯進行擴展,前段時間在梳理插件化框架時,發現當時用的不少思想和插件化的思想不謀而合,如今想來,若是繼續在原有基礎上擴展,添加插件化的思路,可能適用性會更高一些。如今將以前的思路作個記錄,後續有機會能夠繼續完善。服務器
String sdcard = Environment.getExternalStorageDirectory().getAbsolutePath();
String jarPath = sdcard + "/ecoomi/jar/gdt_dex.jar";
File file = new File(jarPath);
if (!file.exists()) {
Log.d("zyl", "file not exist");
return false;
}
String tmpPath = context.getApplicationContext().getDir("Jar", 0).getAbsolutePath();
DexClassLoader cl = new DexClassLoader(jarPath, tmpPath
, tmpPath, context.getClassLoader());
複製代碼
廣點通廣告的構造方法有五個參數,分別是:markdown
這五個參數中,adSize和廣告回調監聽都是廣點通SDK提供,adSize使用反射建立對象,廣告回調監聽使用動態代理替換以前的接口app
try {
// 廣告類
Class<?> gdtClass = cl.loadClass("com.qq.e.ads.nativ.NativeExpressAD");
// adSize類
Class<?> adSizeClass = cl.loadClass("com.qq.e.ads.nativ.ADSize");
// 廣告回調監聽類
Class<?> listenerClass = cl.loadClass("com.qq.e.ads.nativ.NativeExpressAD$NativeExpressADListener");
//AdSize的構造方法
Constructor<?> adSizeConstructor = adSizeClass.getConstructor(int.class, int.class);
//InvokeHander,入參是向app提供回調的自定義callBack
GdtInvokeHandler handler = new GdtInvokeHandler(adCallBack);
// 建立廣告回調監聽的動態代理
Object listener = Proxy.newProxyInstance(cl, new Class[] { listenerClass }, handler);
Log.d("zyl", "listener = " + listener.getClass().getName());
// 獲取廣告類的構造方法
Constructor<?> gdtConstructor = gdtClass.getConstructor(Context.class, adSizeClass, String.class, String.class, listenerClass);
// 根據構造方法建立廣告對象實例
gdtObject = gdtConstructor.newInstance(context, adSizeConstructor.newInstance(-1, -2), APP_KEY, APP_KEY, listener);
//todo 增長視頻廣告支持
// 獲取加載廣告的方法
loadAdMethod = gdtClass.getDeclaredMethod("loadAD", int.class);
loadAdMethod.setAccessible(true);
// 替換系統的ClassLoader爲自定義的
loadApkClassLoader(context, cl);
//加載廣點通所需的activity和Service(這裏應該不須要)
cl.loadClass("com.qq.e.comm.DownloadService");
cl.loadClass("com.qq.e.ads.ADActivity");
cl.loadClass("com.qq.e.ads.PortraitADActivity");
cl.loadClass("com.qq.e.ads.LandscapeADActivity");
Log.d("zyl", "init success");
return true;
} catch (IllegalArgumentException e) {
Log.d("zyl", "IllegalArgumentException = " + e.getMessage());
} catch (IllegalAccessException e) {
Log.d("zyl", "IllegalArgumentException = " + e.getMessage());
} catch (InvocationTargetException e) {
Log.d("zyl", "IllegalArgumentException = " + e.getMessage());
} catch (Exception e) {
Log.d("zyl", "GdtException = " + e.getMessage());
}
複製代碼
GdtInvokeHandler的實現:框架
private AdCallBack mAdCallback;
public GdtInvokeHandler(AdCallBack adCallBack) {
mAdCallback = adCallBack;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Log.d("zyl", "InvokeHandler = " + method.getName());
if ("onADLoaded".equals(method.getName())) {
if (args != null && args.length > 0) {
List list = (List) args[0];
if (list != null && list.size() > 0) {
List<AdInterface> tempList = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
Object object = list.get(i);
GdtModelView view = new GdtModelView(object);
view.render();
tempList.add(view);
}
mAdCallback.onAdLoaded(tempList);
}
}
} else if ("onRenderFail".equals(method.getName())) {
} else if ("onRenderSuccess".equals(method.getName())) {
} else if ("onADExposure".equals(method.getName())) {
} else if ("onADClicked".equals(method.getName())) {
} else if ("onADClosed".equals(method.getName())) {
} else if ("onADLeftApplication".equals(method.getName())) {
} else if ("onADOpenOverlay".equals(method.getName())) {
} else if ("onADCloseOverlay".equals(method.getName())) {
} else if ("onNoAD".equals(method.getName())) {
Object object = args[0];
Class clazz = object.getClass();
try {
Method errorMethod = clazz.getDeclaredMethod("getErrorMsg");
errorMethod.setAccessible(true);
String errorMessage = (String)errorMethod.invoke(object);
Log.d("zyl", "errorMessage = " + errorMessage);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return null;
}
複製代碼
主要是講廣告回調的監聽全都轉到InvokeHandler中並在此處理相關邏輯ide
廣點通廣告須要打開SDK中定義的Activity和Service,若是用自定義的類加載器去加載,它只是加載了Activity和Service相關類,沒有通過AMS的處理,是不可能有生命週期方法、window、view等的建立的,怎麼辦呢,咱們須要讓系統的類加載器能夠加載jar包所在路徑的類,這樣在啓動Activity時,它會有相關處理,變成一個正常的Actiivity/Service。這個邏輯的處理在loadApkClassLoader
方法:spa
private static void loadApkClassLoader(Context context, DexClassLoader dLoader){
try{
Object currentActivityThread = RefInvoke.invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[] {}, new Object[] {});//獲取主線程對象
String packageName = context.getPackageName();//當前apk的包名
ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mPackages");
WeakReference wr = (WeakReference) mPackages.get(packageName);
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
wr.get(), dLoader);
Log.i("zyl", "classloader:"+dLoader);
}catch(Exception e){
Log.i("zyl", "load apk classloader error:"+Log.getStackTraceString(e));
}
}
複製代碼
這時候就有同窗問了,你把這個類加載器替換了,那麼它不就只能加載jar包路徑下的類了麼,那我App中的類豈不是加載不了了?放心,不會有這個問題,看下類加載器的建立:插件
DexClassLoader cl = new DexClassLoader(jarPath, tmpPath
, tmpPath, context.getClassLoader());
複製代碼
最後一個參數parent,它所傳的實參context.getClassLoader(),和LoadedApk中的是同一個,也就是說,咱們定義了一個增強版類加載器,它既能夠加載主APK所在路徑的類,也能夠加載jar包所在路徑的類線程
public void loadGdtAd() {
if (!inited) {
inited = init(mContext, mAdCallBack);
if (!inited) {
return;
}
}
try {
loadAdMethod.invoke(gdtObject, new Object[] {10});
} catch (IllegalAccessException e) {
Log.d("zyl", "loadGdtAd IllegalAccessException = " + e.getMessage());
e.printStackTrace();
} catch (InvocationTargetException e) {
Log.d("zyl", "loadGdtAd InvocationTargetException = " + e.getMessage());
} catch (Exception e) {
Log.d("zyl", "loadGdtAd Exception = " + e.getMessage());
}
}
複製代碼