Android Studio开发安卓插件

转载请注明出处: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文件 就自然报错啦



想了解更多资讯 关注我的微信公众号:


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值