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文件 就自然報錯啦



想了解更多資訊 關注我的微信公衆號:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章