上一篇部落格中http://blog.csdn.net/lxping51/article/details/71480239,主要通過反射的方式來實作動态加載插件化,今天我們以接口的方式來達到目的。接口的實作比反射更為簡單,而且直接調用對性能有很大的提高。但是這也意味着需要處理的麻煩也更多。此文章主要學習來自CSDN部落格大神尼古拉斯-趙四的這篇部落格内容講得很詳細。我的這一篇部落格是在此基礎上,根據自身遇到的問題,加以調整和延伸。接口實作動态加載,對我來說還是比較有挑戰性和成就感的,主要問題不在于接口的一個實作過程,而是其中我的項目中所遇到的要不斷需要改變類包名的挑戰。
接下來,就開始切入正題:
項目還是使用上一篇文章中的兩個:Host(宿主)、LibPlugin(插件)
LibPlugin
1,首先要建立三個接口InterLibPluginActivitys,InterLibPluginReceivers,InterLibPluginServices,為了友善打包特意在每個檔案後多加了個”s”
InterLibPluginActivitys.java
public interface InterFloatActivitys {
public void onCreate(Activity activity, Class<?> mActivityClass, Class<?> mServiceClass, Class<?> mReceiverClass);
public void onStart();
public void onResume();
public void onStop();
public void onDestroy();
public boolean onKeyDown(int keyCode, KeyEvent event);
public boolean onTouchEvent(MotionEvent event);
}
InterLibPluginReceivers.java
public interface InterLipPluginReceivers {
void onReceive(Context context, Intent intent, Class<?> mActivityClass, Class<?> mServiceClass,
Class<?> mReceiverClass);
}
InterLibPluginServices.java
public interface InterLipPluginServices {
void onCreate(Context context, Class<?> mActivityClass, Class<?> mServiceClass, Class<?> mReceiverClass);
int onStartCommand(Intent intent, int flags, int startId);
void onDestroy();
}
2,LibPlugin中的對應實作,也就是上一篇中對應的檔案(例如Service)
public class LibPluginService implements InterLibPluginServices {
public void onCreate(Context context, Class<?> mActivityClass, Class<?> mServiceClass, Class<?> mReceiverClass) {
body...;
}
public int onStartCommand(Intent intent, int flags, int startId) {
body...;
}
public void onDestroy() {
body...;
}
}
3,打包過程依然使用ant打包,配置檔案中對名字處理;
build檔案中不混淆;
4,ant打包成LibPlugin.apk
如果和第一篇中要實作的結果一樣,那麼接下來就更簡單了,有兩種方式:(1),可以直接在宿主Host中建立一個包名和類名相同的檔案(注意包名和類名相同)如下圖:
然後在通過DexClassLoad動态加載擷取到類後,對類對象進行對應的強制轉換,例如 (Service)
public class MyService extends Service {
private InterLibPluginService service;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Class<?> servieclass = Utils.getClasses(this, CLASS_SERVICE, null);
if (servieclass == null)
return;
try {
service = (InterLibPluginService) servieclass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
service.onCreate(this, MyActivity.class, MyService.class, MyReceiver.class);
}
}
通過DexClassLoad擷取類對象的代碼請點選檢視!
(2),在LipPlugin打包過程中,不将接口一起打包而是單獨打成一個jar包,例如lib.jar 此種方式借鑒與尼古拉斯-趙四的部落格http://blog.csdn.net/jiangwei0910410003/article/details/17679823,然後在生命周期中直接轉換和調用,方式和上面相同。
但這并不是我想要的,我希望的目的是做一個sdk來調用插件,然後宿主再調用sdk,其實也就是多了一道程式。雖然隻多了一個,但是整個打包過程,有會麻煩一些。核心問題還是不能混淆這幾個關鍵接口。接下來主要操作Host,如之前提到的更多的是對ant打包的學習。
Host:
1,在com.b.b檔案包下建立接口,内容和上面一樣。
這裡其實有個疑惑:從編譯工具的角度出發,同一個項目中是不允許有兩個類名完全相同的檔案存在的,但是我們之前在編譯LibPlugin的時候其實已經編譯了com.b.b.InterLiaPluginActivity.java –等,此刻我們有再次建立相同接口,盡管項目中不會報錯(因為事實上沒處于同一個檔案夾),但編譯一起的時候,應該處于同一個項目中。這沒有報錯而且很好的執行。這是一個疑點,也在研究中。不過話說回來,通過接口的方式來達到動态加載的目的,如果沒有引入接口,那麼轉化肯定是沒辦法進行的。是以它的可行是必須的。
2,轉化過程參考4-(1)
3,ant配置檔案local-dev.properties 申明三個接口不混淆
########## android sdk相關參數設定##############
#android-jar=E:/android-sdk-windows/platforms/android-7/android.jar
adtpath=D:/adt-bundle-windows-x86_64-20140702/sdk
android-jar=${adtpath}/platforms/android-8/android.jar
dynamic-jar=D:/workspace/svn_box_bar_new_sdk_use_interface/libs/Dynamic_inter.jar
############ 混淆相關參數設定 ############
#proguard-dir=E:/android-sdk-windows/tools/proguard
proguard-dir=${adtpath}/tools/proguard
############ ANT功能擴充參數設定 ############
ant-contrib=D:/AntToJar/ant-contrib-1.0b3.jar
######################################
target-root=dest
##############廣告類型###########
project-name=boxbar
############sdk版本 #######
sdk-version=
#############舊包名##############
old-package-name=com.j.s
dynamic-package-name = com.b.b
###############新包名##########################
new-package-name=com.google.smy
#new-package-name=com.hzd.bdld
###############管理類名,不混淆##########################
manager-name=Em
a-name = InterLibPluginActivity
r-name = InterLibPluginReceiver
s-name = InterLibPluginService
#manager-name=itbl
###############舊類名, 多個時以【;】分割##########################
old-classes=Edt;MyActivity;MyReceiver;MyService
#old-classes=MyManager;MyActivity;MyReceiver;MyService
###############新類名, 多個時以【;】分割##########################
new-classes=Em;Ra;Tr;Ys
#new-classes=itbl;itblAc;itblR;itblS
4,build-dev-sdk.xml
<?xml version="1.0" ?>
<project name="ji_dev_sdk" default="release" basedir=".">
<!-- Load the properties files -->
<property file="local-dev.properties" />
<!-- Input directories -->
<property name="src-dir" value="src" />
<!-- Output directories -->
<property name="out-dir" value="${target-root}/tmp" />
<property name="out-dir-classes" value="${out-dir}/classes" />
<!-- package path -->
<taskdef resource="net/sf/antcontrib/antcontrib.properties">
<classpath>
<pathelement location="${ant-contrib}"/>
</classpath>
</taskdef>
<property name="new-package-path" value="${new-package-name}" />
<propertyregex property="dynamic-package-path" input="${dynamic-package-name}" regexp='\.' replace="\/"/>
<propertyregex property="old-package-path" input="${old-package-name}" regexp='\.' replace="\/"/>
<propertyregex override="true" property="new-package-path" input="${new-package-name}" regexp='\.' replace="\/"/>
<echo>${old-package-path}</echo>
<echo>${new-package-path}</echo>
<echo>${dynamic-package-path}</echo>
<echo>${new-classes}</echo>
<echo>${manager-name}</echo>
<!-- Clean directories -->
<target name="clean-dirs">
<echo>clean-dirs..................</echo>
<delete dir="${target-root}" />
</target>
<!-- Create directories if not exist -->
<target name="mkdirs" depends="clean-dirs">
<echo>mkdirs.................</echo>
<delete dir="${target-root}" />
<mkdir dir="${out-dir}" />
<mkdir dir="${out-dir-classes}" />
<mkdir dir="${target-root}/${dynamic-package-path}" />
<mkdir dir="${target-root}/${old-package-path}" />
<mkdir dir="${target-root}/${new-package-path}" />
</target>
<!-- copy the source files to target directory -->
<target name="copy0" depends="mkdirs">
<copydir dest="${target-root}/${old-package-path}" src="${src-dir}/${old-package-path}">
</copydir>
</target>
<target name="copy1" depends="mkdirs">
<copydir dest="${target-root}/${dynamic-package-path}" src="${src-dir}/${dynamic-package-path}"/>
</target>
<target name="update-classes" depends="copy0,copy1">
<java jar="ClassRenameWithPackage.jar" fork="true" failonerror="true">
<arg value="${target-root}" />
<!-- <arg value="${dynamic-package-name}" /> -->
<arg value="${old-package-name}" />
<arg value="${old-classes}" />
<arg value="${new-classes}" />
</java>
</target>
<target name="copy" depends="update-classes">
<rename dest="${target-root}/${new-package-path}" src="${target-root}/${old-package-path}"/>
</target>
<target name="update-package" depends="copy">
<java jar="package-tool.jar" fork="true" failonerror="true">
<arg value="${target-root}" />
<arg value="${old-package-name}" />
<arg value="${new-package-name}" />
</java>
</target>
<!-- Compiling the java files -->
<target name="compile" depends="update-package">
<echo>Compiling the java files.................</echo>
<javac encoding="utf-8" target="1.6" debug="true" srcdir="${target-root}" destdir="${out-dir-classes}" classpath="${out-dir-classes}" bootclasspath="${android-jar}" includeantruntime="false">
<classpath>
<!--
<fileset dir="libs" includes="*.jar" />
-->
</classpath>
</javac>
<delete>
<fileset dir="${out-dir-classes}/${new-package-path}" includes="MainActivity*.class" />
</delete>
<!--
<copydir dest="${out-dir-classes}/assets" src="assets"></copydir>
-->
<jar basedir="${out-dir-classes}" destfile="temp.jar" />
<!--
<jar destfile="sdk/${project-name}-${new-package-name}-${sdk-version}-test.jar" basedir="${out-dir-classes}"/>
-->
</target>
<target name="proguard" depends="compile">
<echo>proguard..........................</echo>
<java jar="${proguard-dir}/lib/proguard.jar" fork="true" failonerror="true">
<jvmarg value="-Dmaximum.inlined.code.length=32" />
<arg value="-injars temp.jar" />
<arg value="-outjars sdk/temp-${sdk-version}.jar" />
<arg value="-dontpreverify" />
<arg value="-dontoptimize" />
<arg value="-dontusemixedcaseclassnames" />
<arg value="-allowaccessmodification" />
<arg value="-dontskipnonpubliclibraryclassmembers" />
<arg value="-dontskipnonpubliclibraryclasses" />
<arg value="-libraryjars ${android-jar}" />
<!-- <arg value="-libraryjars ${dynamic-jar}"/> -->
<arg value="-optimizationpasses 7" />
<arg value="-verbose" />
<arg value="-keep public class * extends android.app.Activity" />
<arg value="-keep public class * extends android.app.Service" />
<arg value="-keep public class * extends android.content.BroadcastReceiver" />
<arg value="-keep public class * extends android.content.ContentProvider" />
<arg value="-keep public class * extends android.preference.Preference" />
<arg value="-keep public class * extends android.app.Application" />
<arg value="-keepclasseswithmembers class * { native <methods>; }" />
<arg value="-keepclasseswithmembernames class * { public <init>(android.content.Context, android.util.AttributeSet); }" />
<arg value="-keepclasseswithmembernames class * { public <init>(android.content.Context, android.util.AttributeSet, int); }" />
<arg value="-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }" />
<arg value="-keep class ${new-package-name}.${manager-name} { <fields> ; <methods> ; } " />
<arg value="-keep interface ${dynamic-package-name}.${a-name} { <fields> ; <methods> ; }" />
<arg value="-keep interface ${dynamic-package-name}.${r-name} { <fields> ; <methods> ; }" />
<arg value="-keep interface ${dynamic-package-name}.${s-name} { <fields> ; <methods> ; }" />
</java>
<delete file="temp.jar" />
<delete dir="${out-dir-classes}"/>
<mkdir dir="${out-dir-classes}" />
<unjar src="sdk/temp-${sdk-version}.jar" dest="${out-dir-classes}"/>
<jar destfile="sdk/${project-name}-${new-package-name}-${sdk-version}.jar" basedir="${out-dir-classes}"/>
<delete file="sdk/temp-${sdk-version}.jar" />
<delete dir="${target-root}" />
</target>
<target name="release" depends="proguard">
</target>
</project>
不混淆的關鍵代碼:
建立新的檔案路徑:com.b.b
<propertyregex property="dynamic-package-path" input="${dynamic-package-name}" regexp='\.' replace="\/"/>
<mkdir dir="${target-root}/${dynamic-package-path}" />
拷貝:
<target name="copy1" depends="mkdirs">
<copydir dest="${target-root}/${dynamic-package-path}" src="${src-dir}/${dynamic-package-path}"/>
</target>
<target name="update-classes" depends="copy0,copy1">
不混淆:
<arg value="-keep interface ${dynamic-package-name}.${a-name} { <fields> ; <methods> ; }" />
<arg value="-keep interface ${dynamic-package-name}.${r-name} { <fields> ; <methods> ; }" />
<arg value="-keep interface ${dynamic-package-name}.${s-name} { <fields> ; <methods> ; }" />
5,最終的目錄結構
這樣的情況下,就可以在宿主中直接加入該jar包來使用。
整個過程看下來其實并不複雜,關鍵動态加載的内容集中在DexClassLoad的使用,接口這部分更多的是結合自身項目的特殊需求而稍加改變。技術部落格寫得還不是很成熟,鼓勵自己慢慢成長。