Android ClassLoder 基础应用

本文详细介绍DexClassLoader和PathClassLoader的区别及应用实践,通过具体案例对比两种类加载器的使用场景,帮助开发者理解并掌握类加载机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

预备知识:

<1> : adb push的使用;

<2> : eclipse 导出jar包的过程

<3> : dx命令行的使用;


首先介绍ClassLoader类关系,它本身也是继承Object,然而实际开发中用的比较多的是他的子类,继承关系如下:

ClassLoader

extends Object

java.lang.Object

   ↳

java.lang.ClassLoader

 

Known Direct Subclasses

BaseDexClassLoader, SecureClassLoader

 

Known Indirect Subclasses

DexClassLoader, PathClassLoader, URLClassLoader

[以上信息来自:http://developer.android.com/reference/java/lang/ClassLoader.html]

 

DexClassLoader和PathClassLoader 的区别:

PathClassLoader是通过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是通过其静态方法loadDex(path,outpath, 0)得到DexFile对象。这两者的区别在于DexClassLoader需要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来说,就 是PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apkcache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放 出dex文件。另外,PathClassLoader在加载类时调用的是DexFileloadClassBinaryName,而DexClassLoader调用的是loadClass。因此,在使用PathClassLoader时类全名需要用”/”替换”.”.

因为博客中只是提供具体的应用,实际希望能够先去阅读ClassLoader源代码.

下面写一个APP体会一下,步骤如下:

<1> 新建OneplusDynamicloader工程,如下 :

 

<2> : 具体代码如下:

接口类IOneplusDynamic.java:


/**
 * @Description
 * 
 */
package com.oneplus.interfaces;

import android.app.Activity;
import android.content.Context;
/**
 * @author zhibao.liu
 * @Date 2015/11/14
 * @company oneplus.Inc
 */
public interface IOneplusDynamic {

	public void OneplusInit(Context context);
	public void OneplusShowProjectManager();
	public void OneplusShowArtistDesigner();
	public void OneplusShowITProgrammer();
	public void Destory();
	
}

具体实现上面接口类 OneplusDynamic.java:
/**
 * @Description
 * 
 */
package com.oneplus.impl;

import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;

import com.oneplus.interfaces.IOneplusDynamic;

/**
 * @author zhibao.liu
 * @Date 2015/11/14
 * @company oneplus.Inc
 */

public class OneplusDynamic implements IOneplusDynamic {

	private final static String TAG="OneplusDynamic";
	
	private Context mContext;
	@Override
	public void OneplusInit(Context context) {
		// TODO Auto-generated method stub
		
		Log.i(TAG,"OneplusDynamic OneplusInit ...");
		mContext=context;
		
	}

	@Override
	public void OneplusShowProjectManager() {
		// TODO Auto-generated method stub
		
		Log.i(TAG,"OneplusDynamic OneplusShowProjectManager ...");
		
	}

	@Override
	public void OneplusShowArtistDesigner() {
		// TODO Auto-generated method stub
		
		Log.i(TAG,"OneplusDynamic OneplusShowArtistDesigner ...");
		
	}

	@Override
	public void OneplusShowITProgrammer() {
		// TODO Auto-generated method stub
		
		Log.i(TAG,"OneplusDynamic OneplusShowITProgrammer ...");
		
		if(mContext==null){
			return ;
		}
		
		Toast.makeText(mContext, "IT programmer is running !", Toast.LENGTH_LONG).show();
		
	}

	@Override
	public void Destory() {
		// TODO Auto-generated method stub
		
		Log.i(TAG,"Dynamic Destory ...");
		if(mContext!=null){
			mContext=null;
		}
		
	}

}

暂时不用理会MainActivity.java和布局文件等.

<3> : 首先将<2>中OneplusDynamic.java打包成jar输出:

Eclipse步骤 :

<a>: File->Export->java->jar输出,如下图:



<b> : 下一步,选择需要输出jar的文件,并且制定输出路径,如下图:

点击Finish 按钮.

<c> : 在电脑C盘生成 oneplusdynamic.jar包,将这个jar拷贝开发androidapp的SDK包下面的build-tools/android-*.*目录下,比如我的目录:

<d> : 启动cmd终端,切换到<c>路径中,执行dx –dex –outputoneplus_dynamic.jar oneplusdynamic.jar

执行上面以后,在同名目录下产生一个新的oneplus_dynamic.jar包,如下:


上面执行主要做的工作是:首先将dynamic.jar编译成dynamic.dex文件(Android虚拟机认识的字节码文件),然后再将 dynamic.dex文件压缩成dynamic_temp.jar,当然你也可以压缩成.zip格式的,或者直接编译成.apk文件都可以的(看了ClassLoader源代码,会发现其实zip,apk,jar其实本质都是一样的---个人之见).

<e> : 同样将工程中interface包打成jar包,然后同样执行dx命令行,结果执行得到oneplus_dynamicinterface.jar(加入前面导出的jar包名为oneplusdynamicinterface.jar) .


后续还需要提前再修改这个工程几个地方,如下 :

删除 : [删除下面是为了这个工程安装到系统就没有必须显示给user,也不需要运行,相当于plugin]

<category android:name="android.intent.category.LAUNCHER" />
android:icon="@drawable/ic_launcher"

增加一个Action :

<action android:name="com.oneplus.impl.OneplusDynamic"/>

主配置文件如下 :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.oneplus.oneplusdynamicloader"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.oneplus.oneplusdynamicloader.OneplusDynamicLoadClassActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
				<action android:name="com.oneplus.impl.OneplusDynamic"/>
                
            </intent-filter>
        </activity>
    </application>

</manifest>

<4> : 再新建一个测试工程OneplusDynamicLoaderTest,工程树如下 :



注意 : 里面interface包和接口是完全将前面里面的抄过来的,复制一份到宿主机APP中.

主工程类文件OneplusDynamicLoadClassActivity.java

/**
 * @Description
 * 
 */
package com.oneplus.oneplusdynamicloadertest;

import java.io.File;
import java.util.List;

import com.oneplus.interfaces.IOneplusDynamic;

import android.os.Bundle;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

/**
 * @author zhibao.liu
 * @Date 2015/11/14
 * @company oneplus.Inc
 */
public class OneplusDynamicLoadClassActivity extends Activity implements
		OnClickListener {

	private final static String TAG = "OneplusDynamicLoadClassActivity";

	private Button mButtonShowPm;
	private Button mButtonShowArt;
	private Button mButtonShowIT;

	private TextView mText;
	
	private Context mContext;

	private IOneplusDynamic mDexLib;
	private IOneplusDynamic mPathLib;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.oneplus_main);

		mContext = OneplusDynamicLoadClassActivity.this;

		mButtonShowPm = (Button) findViewById(R.id.showpm);
		mButtonShowPm.setOnClickListener(this);

		mButtonShowArt = (Button) findViewById(R.id.showart);
		mButtonShowArt.setOnClickListener(this);

		mButtonShowIT = (Button) findViewById(R.id.showit);
		mButtonShowIT.setOnClickListener(this);
		
		mText=(TextView)findViewById(R.id.showinfo);
		
		/*DexClassLoaderX();*/
		PathClassLoaderX();

	}

	private void DexClassLoaderX() {

		/** 使用DexClassLoader方式加载类 */
		// dex压缩文件的路径(可以是apk,jar,zip格式)
		String dexPath = Environment.getExternalStorageDirectory().toString()
				+ File.separator + "OneplusDynamicLoader.apk";
		// dex解压释放后的目录
		// String dexOutputDir = getApplicationInfo().dataDir;
		String dexOutputDirs = Environment.getExternalStorageDirectory()
				.toString();
		// 定义DexClassLoader
		// 第一个参数:是dex压缩文件的路径
		// 第二个参数:是dex解压缩后存放的目录
		// 第三个参数:是C/C++依赖的本地库文件目录,可以为null
		// 第四个参数:是上一级的类加载器
		DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDirs, null,
				getClassLoader());

		// com.dynamic.impl.Dynamic是动态类名
		// 使用DexClassLoader加载类
		try {
			Class libProviderClazz = cl
					.loadClass("com.oneplus.impl.OneplusDynamic");
			try {
				mDexLib = (IOneplusDynamic) libProviderClazz.newInstance();
				mDexLib.OneplusInit(mContext);
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	private void PathClassLoaderX() {

		/** 使用PathClassLoader方法加载类 */
		// 创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>
		Intent intent = new Intent("com.oneplus.impl.OneplusDynamic", null);
		// 获得包管理器
		PackageManager pm = getPackageManager();
		List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
		// 获得指定的activity的信息
		ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
		// 获得apk的目录或者jar的目录
		String apkPath = actInfo.applicationInfo.sourceDir;
		// native代码的目录
		String libPath = actInfo.applicationInfo.nativeLibraryDir;
		// 创建类加载器,把dex加载到虚拟机中
		// 第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取
		// 第二个参数:是C/C++依赖的本地库文件目录,可以为null
		// 第三个参数:是上一级的类加载器
		PathClassLoader pcl = new PathClassLoader(apkPath, libPath,
				this.getClassLoader());
		// 加载类

		Class libProviderClazz;
		try {

			libProviderClazz = pcl.loadClass("com.oneplus.impl.OneplusDynamic");
			try {
				mPathLib = (IOneplusDynamic) libProviderClazz.newInstance();
				if (mPathLib != null && mContext != null) {
					mPathLib.OneplusInit(mContext);
				}
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	@Override
	public void onClick(View view) {
		// TODO Auto-generated method stub
		
		int id = view.getId();

		switch (id) {
		case R.id.showpm:
			if (mPathLib != null) {
				mPathLib.OneplusShowProjectManager();
			}
			break;
		case R.id.showart:
			if (mPathLib != null) {
				mPathLib.OneplusShowArtistDesigner();
			}
			break;
		case R.id.showit:
			if (mPathLib != null) {
				mPathLib.OneplusShowITProgrammer();
			}
			break;

		}

	}

}

对应布局文件oneplus_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/showinfo"
        />
    
    <Button 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/showpm"
        android:text="@string/oneplus_showpm"/>
    
    <Button 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/showart"
        android:text="@string/oneplus_showart"/>
    
    <Button 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/showit"
        android:text="@string/oneplus_showit"/>
    

</LinearLayout>

Values 文件string.xml 下新增下面:

<string name="oneplus_showpm">Show PM</string>
<string name="oneplus_showart">Show ART</string>
<string name="oneplus_showit">Show IT</string>

运行程序,点击app按钮 :

结果就出来了,当点击”Show IT”按钮将会显示Toast气泡.

 

不过读者会发现上面我们只是用PathClassLoader类,但是DexClassLoader类没使用呀.

由于DexClassLoaderX()方式是使用路径加载的方式,程序中给出的路径是:

String dexPath = Environment.getExternalStorageDirectory().toString()
				+ File.separator + "OneplusDynamicLoader.apk";

那么这个不需要安装,将被加载的对象编译生成的APK放到手机SD卡中,push到手机sdcard目录下,现在来分析一下下面DexClassLoaderX方法的程序:

private void DexClassLoaderX() {

		/** 使用DexClassLoader方式加载类 */
		// dex压缩文件的路径(可以是apk,jar,zip格式)
		//由于oneplus_dynamic.jar被执行dx过,其实就是dex文件,所以正如上面所说,这时oneplus_dynamic.jar和OneplusDynamicLoader.apk对DexClassLoader来说是没有区别的.
		String dexPath = Environment.getExternalStorageDirectory().toString()
				+ File.separator + "oneplus_dynamic.jar";//"OneplusDynamicLoader.apk";
		
		File file=new File(dexPath);
		
		if(!file.exists()){
			return ;
		}

		// dex解压释放后的目录
		// 这个路径在4.2版本以后是存在问题的,因为android系统会认为这个dexOutputDirs这个路径是一个random direction,将不会允许app访问,会报异常错误的
		String dexOutputDirs = Environment.getExternalStorageDirectory()
				.toString(); // this is not access permission to read/write random file
		//所以实际上需要下面的方式获取,这一点非常重要!!!
		final File optimizedDexOutputPath = getDir("outdex", 0);
		
		// 定义DexClassLoader
		// 第一个参数:是dex压缩文件的路径
		// 第二个参数:是dex解压缩后存放的目录
		// 第三个参数:是C/C++依赖的本地库文件目录,可以为null
		// 第四个参数:是上一级的类加载器
		DexClassLoader cl = new DexClassLoader(dexPath, optimizedDexOutputPath.getAbsolutePath(), null,
				getClassLoader());

		// com.dynamic.impl.Dynamic是动态类名
		// 使用DexClassLoader加载类
		try {
			Class libProviderClazz = cl
					.loadClass("com.oneplus.impl.OneplusDynamic");
			try {
				mDexLib = (IOneplusDynamic) libProviderClazz.newInstance();
				mDexLib.OneplusInit(mContext);
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

所以将上面的程序替换前面的程序,程序即可以work了,效果时一样的.

下面在附录 :

<1> : ClassLoader 源码片段供读者参考:

/**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * The path lists are separated using the character specified by
     * the "path.separator" system property, which defaults to ":".
     *
     * @param dexPath
     *  the list of jar/apk files containing classes and resources
     * @param dexOutputDir
     *  directory where optimized DEX files should be written
     * @param libPath
     *  the list of directories containing native libraries; may be null
     * @param parent
     *  the parent class loader
     */
    public DexClassLoader(String dexPath, String dexOutputDir, String libPath,
        ClassLoader parent) {

        super(parent);

        if (dexPath == null || dexOutputDir == null)
            throw new NullPointerException();

        mRawDexPath = dexPath;
        mDexOutputPath = dexOutputDir;
        mRawLibPath = libPath;

        String[] dexPathList = mRawDexPath.split(":");
        int length = dexPathList.length;

        //System.out.println("DexClassLoader: " + dexPathList);
        mFiles = new File[length];
        mZips = new ZipFile[length];
        mDexs = new DexFile[length];

        /* open all Zip and DEX files up front */
        for (int i = 0; i < length; i++) {
            //System.out.println("My path is: " + dexPathList[i]);
            File pathFile = new File(dexPathList[i]);
            mFiles[i] = pathFile;

            if (pathFile.isFile()) {
                try {
                    mZips[i] = new ZipFile(pathFile);
                } catch (IOException ioex) {
                    // expecting IOException and ZipException
                    System.out.println("Failed opening '" + pathFile
                        + "': " + ioex);
                    //ioex.printStackTrace();
                }

                /* we need both DEX and Zip, because dex has no resources */
                try {
                    String outputName =
                        generateOutputName(dexPathList[i], mDexOutputPath);
                    mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0);
                } catch (IOException ioex) {
                    // might be a resource-only zip
                    System.out.println("Failed loadDex '" + pathFile
                        + "': " + ioex);
                }
            } else {
                if (VERBOSE_DEBUG)
                    System.out.println("Not found: " + pathFile.getPath());
            }
        }

        /*
         * Prep for native library loading.
         */
        String pathList = System.getProperty("java.library.path", ".");
        String pathSep = System.getProperty("path.separator", ":");
        String fileSep = System.getProperty("file.separator", "/");

        if (mRawLibPath != null) {
            if (pathList.length() > 0) {
                pathList += pathSep + mRawLibPath;
            }
            else {
                pathList = mRawLibPath;
            }
        }

        mLibPaths = pathList.split(pathSep);
        length = mLibPaths.length;

        // Add a '/' to the end so we don't have to do the property lookup
        // and concatenation later.
        for (int i = 0; i < length; i++) {
            if (!mLibPaths[i].endsWith(fileSep))
                mLibPaths[i] += fileSep;
            if (VERBOSE_DEBUG)
                System.out.println("Native lib path " +i+ ":  " + mLibPaths[i]);
        }
    }























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值