編輯:關於Android編程
類加載器(class loader)是 Java?中的一個很重要的概念。類加載器負責加載 Java 類的字節代碼到 Java 虛擬機中。
Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創建出該類的一個對象。實際的情況可能更加復雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。
與JVM不同,Dalvik的虛擬機不能用ClassCload直接加載.dex,Android從ClassLoader派生出了兩個類:DexClassLoader和PathClassLoader;而這兩個類就是我們加載dex文件的關鍵,這兩者的區別是:
1.DexClassLoader:可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk;
2.PathClassLoader:要傳入系統中apk的存放Path,所以只能加載已經安裝的apk文件。
工程目錄是這樣的:
動態加載進來的class如何使用,一般有2種辦法,一種是使用反射調用,這種我不多做介紹;還有一種是使用接口編程的方式來調用對應的方法,畢竟.dex文件也是我們自己維護的,所以可以把方法抽象成公共接口,把這些接口也復制到主項目裡面去,就可以通過這些接口調用動態加載得到的實例的方法了。
接下來我們源碼包下面新建一個包名稱是dynamic,然後在dynamic下新建一個interface接口Dynamic,裡面有個接口方法,就叫sayHello()吧,返回一個String,到時候我們可以通過Toast彈出來,Dynamic.java:
package wangyang.zun.com.mydexdemo.dynamic;
/**
* Created by WangYang on 2016/3/11.
*/
public interface Dynamic {
String sayHello();
}
接著我們新建一個impl包,並實現Dynamic接口,DynamicImpl.java:
package wangyang.zun.com.mydexdemo.dynamic.impl;
import wangyang.zun.com.mydexdemo.dynamic.Dynamic;
/**
* Created by WangYang on 2016/3/11.
*/
public class DynamicImpl implements Dynamic {
@Override
public String sayHello() {
return new StringBuilder(getClass().getName()).append(" is loaded by DexClassLoader").toString();
}
}
很簡單輸出一句話,"DynamicImpl is loaded by DexClassLoader."
具體的工程目錄如下圖:
點擊Build -> make project,這時候會在build\intermediates\classes\debug目錄下生成對應的classes文件。
好了我們要把DynamicImpl這個class轉換成Dalvik可識別的dex文件,分兩步:
1.先導出DynamicImpl這個類為jar包的形式;
2.通過android sdk自帶的dx.jar工具轉換jar包為dex文件。
完成第一步,當時遇到點麻煩,由於eclipse是基於ant並且有可視化工具,可以直接導出指定文件的jar包,但是android studio不行,那怎麼辦呢?
我們可以通過gradle task來打包,打開app目錄下的build.gradle文件,切記不是根目錄的build.gradle文件,加上以下代碼:
//刪除dynamic.jar包任務
task clearJar(type: Delete) {
delete 'libs/dynamic.jar'
}
//打包任務
task makeJar(type:org.gradle.api.tasks.bundling.Jar) {
//指定生成的jar名
baseName 'dynamic'
//從哪裡打包class文件
from('build/intermediates/classes/debug/wangyang/zun/com/mydexdemo/dynamic/')
//打包到jar後的目錄結構
into('wangyang/zun/com/mydexdemo/dynamic/')
//去掉不需要打包的目錄和文件
exclude('test/', 'Dynamic.class', 'BuildConfig.class', 'R.class')
//去掉R$開頭的文件
exclude{ it.name.startsWith('R$');}
}
makeJar.dependsOn(clearJar, build)
output是你的輸出目錄,默認就是在當前的根目錄下,執行完成後我們就在當前目錄下生成了Davilk虛擬機可執行的dex文件,因為這條命令同時會打包dex文件,因此後綴是jar,我們用jd-gui打開dynamic.jar和dynamic_dex.jar這兩個文件,看下他們有的結構。
可以看到,打包後的文件其實是一個classes.dex文件,目前為止我們要做的工作已經准備就緒了,接下來就是要在demo中使用這個dex文件。
二、刪除剛剛新建的impl包以及包內的文件:
因為等下我們要使用的是dex下面的IDynamic實現類,所以我們需要刪除當前工程下的IDynamic.java文件和impl包,避免運行時出錯。同時,我們要把剛剛生成的dynamic_dex.jar文件放到assets目錄下,等下需要把它copy到app/data下使用,刪除後的整個工程目錄如下:
FileUtils類是從assets目錄下copy文件到app/data/cache目錄,源碼如下:
public class FileUtils {
public static void copyFiles(Context context, String fileName, File desFile) {
InputStream in = null;
OutputStream out = null;
try {
in = context.getApplicationContext().getAssets().open(fileName);
out = new FileOutputStream(desFile.getAbsolutePath());
byte[] bytes = new byte[1024];
int i;
while ((i = in.read(bytes)) != -1)
out.write(bytes, 0 , i);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (in != null)
in.close();
if (out != null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static boolean hasExternalStorage() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
/**
* 獲取緩存路徑
*
* @param context
* @return 返回緩存文件路徑
*/
public static File getCacheDir(Context context) {
File cache;
if (hasExternalStorage()) {
cache = context.getExternalCacheDir();
} else {
cache = context.getCacheDir();
}
if (!cache.exists())
cache.mkdirs();
return cache;
}
}
打開MainActivity:
public class MainActivity extends AppCompatActivity {
private Dynamic dynamic;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//添加一個點擊事件
findViewById(R.id.tx).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadDexClass();
}
});
}
/**
* 加載dex文件中的class,並調用其中的sayHello方法
*/
private void loadDexClass() {
File cacheFile = FileUtils.getCacheDir(getApplicationContext());
String internalPath = cacheFile.getAbsolutePath() + File.separator + "dynamic_dex.jar";
File desFile = new File(internalPath);
try {
if (!desFile.exists()) {
desFile.createNewFile();
FileUtils.copyFiles(this, "dynamic_dex.jar", desFile);
}
} catch (IOException e) {
e.printStackTrace();
}
//下面開始加載dex class
DexClassLoader dexClassLoader = new DexClassLoader(internalPath, cacheFile.getAbsolutePath(), null, getClassLoader());
try {
Class libClazz = dexClassLoader.loadClass("wangyang.zun.com.mydexdemo.dynamic.impl.IDynamic");
dynamic = (Dynamic) libClazz.newInstance();
if (dynamic != null)
Toast.makeText(this, dynamic.sayHelloy(), Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序運行的效果圖如下:
至此,我們關於Android Dex動態加載機制的原理講到這裡,接下來我會分析下通過Dex實現熱修復的基本原理。
Andorid4.x 流氓式屏蔽HOME鍵
應用項目需要要屏蔽HOME鍵。項目本身的要求是讓按下HOME鍵後程序不做任何響應,就像按下返回鍵一樣在onBackPressed 方法中直接return啥都
《極簡筆記》源碼分析(一)
0. 介紹此文將對Github上lguipeng大神所開發的 極簡筆記 v2.0 (點我下載源碼)代碼進行分析學習。通過此文你將學到:應用源碼的研讀方法 MVP架構模式
一分鐘了解Android屏幕 ldpi mdpi hdpi xhdpi
DPI:每英寸像素數簡單的屏幕分辨率計算方法:DisplayMetrics metrics = new DisplayMetrics();Display display
Android應用開發中自定義ViewGroup的究極攻略
支持margin,gravity以及水平,垂直排列最近在學習android的view部分,於是動手實現了一個類似ViewPager的可上下或者左右拖動的ViewGroup