转载请注明出处: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(); } }
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
好了我们现在开始编写客户端的程序啦
导入刚刚生成的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; }
想了解更多资讯 关注我的微信公众号: