转载请注明出处: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文件 就自然报错啦
想了解更多资讯 关注我的微信公众号: