轉載請註明出處:http://blog.csdn.net/u013022222/article/details/50242287
看了看網上所有關於開發安卓插件的文章,大多是圍繞eclipse的開發環境開始的,關於as的實在是太少而且其中還是存在不少的錯誤的。處於對這門熱門技術的好奇,我花了兩天時間做了一個簡單的demo,差不多已經可以慢慢起步開發微信搶紅包的插件了
首先我們先開始做一個簡單地demo,能夠動態的加載代碼,到這裏的時候,我希望讀者能夠了解java的類加載過程,因爲其中涉及的知識還是很多的,我之前閱讀了不少大神的博客,現在分享出去:http://blog.csdn.net/jiangwei0910410003/article/details/17679823
首先我們先建立一個android 工程,這個步驟和平時的步驟並沒有什麼區別
之後創建你得插件工程 ,剛剛創建的是正常的安卓工程,有界面有各種資源,但是作爲插件模塊的話,可能會沒有那些功能,在android studio裏面這被叫做module
所以點擊file->new->new module創建一個新的模塊
這裏便是是我建立的一個名爲pluginlib的模塊
好了我們已經做完所有的工作 ,開始正式編寫我們的代碼。
在現實的開發中,我們會遇到一些這樣的情況:我們應用有些核心代碼是不能打包到apk中的,因爲apk很容易被破解,所以,我們希望能夠動態的加載代碼,只在運行的時候加載,然後關閉應用的時候我們再刪除那些代碼。還有一種情況就是,我們希望有些功能更新的時候,不能老是提醒用戶去更新,這樣很容易讓用戶產生厭煩情緒,對於這些情況,我們動態加載技術能夠很好的平衡和用戶之間的矛盾。
既然能夠讓用戶在不需要更新客戶端的情況下使用新的功能,我們怎麼做呢。這時候腦海裏首先想到的就是,我們能否定義一個接口,這個接口是穩定的,不會隨着版本的發行而改變,對應的我們Implementation類的話,是可以改變的,用戶無法察覺到底發生了什麼,天哪簡直完美,so,let's do it.
我們新建兩個包 一個名爲interfaces,一個名爲impl,前者我們存放用於集成到客戶端的接口,後面我們存放實現類,之後如果我們更改代碼的話都是在impl裏面進行修改,按照契約嘛,接口都是穩定的,我們不會去修改他
show u the code:
接口:
package com.os.pluginlib.interfaces;
import android.content.Context;
/**
* Created by chan on 15/12/9.
*/
public interface DemoInterface {
void init(Context context);
void sayHello();
}
實現類:
package com.os.pluginlib.impl;
import android.content.Context;
import android.widget.Toast;
import com.os.pluginlib.interfaces.DemoInterface;
/**
* Created by chan on 15/12/9.
*/
public class DemoImpl implements DemoInterface {
private Context m_context;
@Override
public void init(Context context) {
m_context = context;
}
@Override
public void sayHello() {
Toast.makeText(m_context,"hello world",Toast.LENGTH_SHORT).show();
}
}
之後就是把這個module編譯下了,注意,在默認情況下,android studio都是生成的debug代碼,爲了獲得module的release代碼,我們需要使用gradle腳本,包含以下模塊
build 一下就有代碼生成了
這時候 我們可以開始打包class文件了,但是,打包的時候,我們是不能把實現類和接口打包在一起的,原因的話我摘錄一段官方的註釋來解釋,不過不是現在,因爲現在講了的話還是不能理解
那我們現在開始打包jar文件吧
在你的 工程名/模塊名/build/intermediates/classes/release 下你會看到生成的class代碼,我們需要分包進行打包
調出終端,我們用自帶的jar工具進行打包
好的,現在我們已經生成了客戶端要集成的sdk.jar 還有放在服務器的代碼lib.jar
不過要知道,安卓上的java虛擬機還是有區別的,他只能運行dex文件,所以即使現在成功打包了lib.jar文件,還是沒用,我們必須要把它轉換成dex的文件,這樣安卓客戶端才能在運行時進行動態加載
在安卓sdk的工具包裏有一個dx工具,他的功能就是把普通的jar文件轉換成dex文件
命令行如下:
dx --dex --output=libx.jar lib.jar
lib.jar是我們剛剛生成的普通Jar文件,libx.jar是放在服務器端用於客戶端動態加載的jar文件
好了我們現在開始編寫客戶端的程序啦
導入剛剛生成的sdk.jar
集成一下功能就行
package com.os.chan.pluginsdk;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import com.os.pluginlib.interfaces.DemoInterface;
import dalvik.system.DexClassLoader;
public class MainActivity extends AppCompatActivity {
private DemoInterface m_demoInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//dx工具生成的jar包存放的位置
final String from = Environment.getExternalStorageDirectory() + "/libx.jar";
Log.d("chan_debug",from);
//在4.0之後的版本 爲了防止代碼注入 dex文件只能解壓到沙盒裏面 解壓到sd卡中會報錯
final String to = getDir("dx",0).getAbsolutePath();
//DexClassLoader是安卓裏面的類加載器
//它能夠動態的加載 apk jar zip文件
//所以對應這樣的話 它需要一個緩衝目錄 來解壓zip apk釋放出來的dex文件
//這也就對應了他的第一個 第二個參數
//分別爲jar,zip,apk文件的存放位置
//to 爲解壓的位置
//第三個爲lib的位置 可以爲空 裏面存放so文件的
//第四個是父類加載器 默認爲系統的PathClassLoader
DexClassLoader dexClassLoader= new DexClassLoader(from,to,null,getClassLoader());
try {
Class<?> loadClass = dexClassLoader.loadClass("com.os.pluginlib.impl.DemoImpl ");
m_demoInterface = (DemoInterface) loadClass.newInstance();
m_demoInterface.init(this);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
findViewById(R.id.id_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(m_demoInterface != null)
m_demoInterface.sayHello();
}
});
}
}
不過如果你是在同一個Project下做實驗,就像我現在這樣,會遇到這種情況
只要記得把剛剛修改的gradle腳本的最後一行註釋掉就行,like this
/*
* Find the class corresponding to "classIdx", which maps to a class name
* string. It might be in the same DEX file as "referrer", in a different
* DEX file, generated by a class loader, or generated by the VM (e.g.
* array classes).
*
* Because the DexTypeId is associated with the referring class' DEX file,
* we may have to resolve the same class more than once if it's referred
* to from classes in multiple DEX files. This is a necessary property for
* DEX files associated with different class loaders.
*
* We cache a copy of the lookup in the DexFile's "resolved class" table,
* so future references to "classIdx" are faster.
*
* Note that "referrer" may be in the process of being linked.
*
* Traditional VMs might do access checks here, but in Dalvik the class
* "constant pool" is shared between all classes in the DEX file. We rely
* on the verifier to do the checks for us.
*
* Does not initialize the class.
*
* "fromUnverifiedConstant" should only be set if this call is the direct
* result of executing a "const-class" or "instance-of" instruction, which
* use class constants not resolved by the bytecode verifier.
*
* Returns NULL with an exception raised on failure.
*/
ClassObject* dvmResolveClass(const ClassObject* referrer, u4 classIdx,
bool fromUnverifiedConstant)
{
DvmDex* pDvmDex = referrer->pDvmDex;
ClassObject* resClass;
const char* className;
/*
* Check the table first -- this gets called from the other "resolve"
* methods.
*/
resClass = dvmDexGetResolvedClass(pDvmDex, classIdx);
if (resClass != NULL)
return resClass;
LOGVV("--- resolving class %u (referrer=%s cl=%p)\n",
classIdx, referrer->descriptor, referrer->classLoader);
/*
* Class hasn't been loaded yet, or is in the process of being loaded
* and initialized now. Try to get a copy. If we find one, put the
* pointer in the DexTypeId. There isn't a race condition here --
* 32-bit writes are guaranteed atomic on all target platforms. Worst
* case we have two threads storing the same value.
*
* If this is an array class, we'll generate it here.
*/
className = dexStringByTypeIdx(pDvmDex->pDexFile, classIdx);
if (className[0] != '\0' && className[1] == '\0') {
/* primitive type */
resClass = dvmFindPrimitiveClass(className[0]);
} else {
resClass = dvmFindClassNoInit(className, referrer->classLoader);
}
if (resClass != NULL) {
/*
* If the referrer was pre-verified, the resolved class must come
* from the same DEX or from a bootstrap class. The pre-verifier
* makes assumptions that could be invalidated by a wacky class
* loader. (See the notes at the top of oo/Class.c.)
*
* The verifier does *not* fail a class for using a const-class
* or instance-of instruction referring to an unresolveable class,
* because the result of the instruction is simply a Class object
* or boolean -- there's no need to resolve the class object during
* verification. Instance field and virtual method accesses can
* break dangerously if we get the wrong class, but const-class and
* instance-of are only interesting at execution time. So, if we
* we got here as part of executing one of the "unverified class"
* instructions, we skip the additional check.
*
* Ditto for class references from annotations and exception
* handler lists.
*/
if (!fromUnverifiedConstant &&
IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED))
{
ClassObject* resClassCheck = resClass;
if (dvmIsArrayClass(resClassCheck))
resClassCheck = resClassCheck->elementClass;
if (referrer->pDvmDex != resClassCheck->pDvmDex &&
resClassCheck->classLoader != NULL)
{
LOGW("Class resolved by unexpected DEX:"
" %s(%p):%p ref [%s] %s(%p):%p\n",
referrer->descriptor, referrer->classLoader,
referrer->pDvmDex,
resClass->descriptor, resClassCheck->descriptor,
resClassCheck->classLoader, resClassCheck->pDvmDex);
LOGW("(%s had used a different %s during pre-verification)\n",
referrer->descriptor, resClass->descriptor);
dvmThrowException("Ljava/lang/IllegalAccessError;",
"Class ref in pre-verified class resolved to unexpected "
"implementation");
return NULL;
}
}
LOGVV("##### +ResolveClass(%s): referrer=%s dex=%p ldr=%p ref=%d\n",
resClass->descriptor, referrer->descriptor, referrer->pDvmDex,
referrer->classLoader, classIdx);
/*
* Add what we found to the list so we can skip the class search
* next time through.
*
* TODO: should we be doing this when fromUnverifiedConstant==true?
* (see comments at top of oo/Class.c)
*/
dvmDexSetResolvedClass(pDvmDex, classIdx, resClass);
} else {
/* not found, exception should be raised */
LOGVV("Class not found: %s\n",
dexStringByTypeIdx(pDvmDex->pDexFile, classIdx));
assert(dvmCheckException(dvmThreadSelf()));
}
return resClass;
}
從第六行開始閱讀就會發現,如果同一個類加載器去加載不同dex文件中相同的class時 便會報錯,而上面的代碼我們可以看到用DexClassLoader加載的時候,指定了一個父類加載器,和java一樣,在安卓裏面類加載器都是委託機制的,他首先會讓父類加載器去找類,找到了就算了,沒找到就自己來,而這時候,如果我們都是通過apk進行安裝代碼的話,指定的類加載器都是同一個PathClassLoader,而它在找class的時候,發現在兩個apk包裏面都找到了相同的class文件 就自然報錯啦
想了解更多資訊 關注我的微信公衆號: