編輯:關於Android編程
關於插件,已經在各大平台上出現過很多,eclipse插件、chrome插件、3dmax插件,所有這些插件大概都為了在一個主程序中實現比較通用的功能,把業務相關或者讓可以讓用戶自定義擴展的功能不附加在主程序中,主程序可在運行時安裝和卸載。在android如何實現插件也已經被廣泛傳播,實現的原理都是實現一套插件接口,把插件實現編成apk或者dex,然後在運行時使用DexClassLoader動態加載進來,不過在這個開發過程中會遇到很多的問題,所以這一片就先不介紹如何開發插件,而是先解決一下開發過程中會遇到的問題,這裡主要就是介紹DexClassLoader這個類使用的過程中出現的錯誤
Java中的類加載器:http://blog.csdn.net/jiangwei0910410003/article/details/17733153
Android中的動態加載機制:http://blog.csdn.net/jiangwei0910410003/article/details/17679823
Android中的各種加載器介紹
插件開發的過程中DexClassLoader和PathClassLoader這兩個類加載器了是很重要的,但是他們也是有區別的,而且我們也知道PathClassLoader是Android應用中的默認加載器。他們的區別是:
DexClassLoader可以加載任何路徑的apk/dex/jar
PathClassLoader只能加載/data/app中的apk,也就是已經安裝到手機中的apk。這個也是PathClassLoader作為默認的類加載器的原因,因為一般程序都是安裝了,在打開,這時候PathClassLoader就去加載指定的apk(解壓成dex,然後在優化成odex)就可以了。
我們可以看一下他們的源碼:
DexClassLoader.java
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dalvik.system;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.zip.ZipFile;
/**
* Provides a simple {@link ClassLoader} implementation that operates on a
* list of jar/apk files with classes.dex entries. The directory that
* holds the optimized form of the files is specified explicitly. This
* can be used to execute code not installed as part of an application.
*
* The best place to put the optimized DEX files is in app-specific
* storage, so that removal of the app will automatically remove the
* optimized DEX files. If other storage is used (e.g. /sdcard), the
* app may not have an opportunity to remove them.
*/
public class DexClassLoader extends ClassLoader {
private static final boolean VERBOSE_DEBUG = false;
/* constructor args, held for init */
private final String mRawDexPath;
private final String mRawLibPath;
private final String mDexOutputPath;
/*
* Parallel arrays for jar/apk files.
*
* (could stuff these into an object and have a single array;
* improves clarity but adds overhead)
*/
private final File[] mFiles; // source file Files, for rsrc URLs
private final ZipFile[] mZips; // source zip files, with resources
private final DexFile[] mDexs; // opened, prepped DEX files
/**
* Native library path.
*/
private final String[] mLibPaths;
/**
* 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);
......我們看到,他是繼承了ClassLoader類的,ClassLoader是類加載器的鼻祖類。同時我們也會發現DexClassLoader只有一個構造函數,而且這個構造函數是:dexPath、dexOutDir、libPath、parentdexPath:是加載apk/dex/jar的路徑
dexOutDir:是dex的輸出路徑(因為加載apk/jar的時候會解壓除dex文件,這個路徑就是保存dex文件的)
libPath:是加載的時候需要用到的lib庫,這個一般不用
parent:給DexClassLoader指定父加載器
我們在來看一下PathClassLoader的源碼
PathClassLoader.java
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dalvik.system;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
public class PathClassLoader extends ClassLoader {
private final String path;
private final String libPath;
/*
* Parallel arrays for jar/apk files.
*
* (could stuff these into an object and have a single array;
* improves clarity but adds overhead)
*/
private final String[] mPaths;
private final File[] mFiles;
private final ZipFile[] mZips;
private final DexFile[] mDexs;
/**
* Native library path.
*/
private final List libraryPathElements;
/**
* Creates a {@code PathClassLoader} that operates on a given list of files
* and directories. This method is equivalent to calling
* {@link #PathClassLoader(String, String, ClassLoader)} with a
* {@code null} value for the second argument (see description there).
*
* @param path
* the list of files and directories
*
* @param parent
* the parent class loader
*/
public PathClassLoader(String path, ClassLoader parent) {
this(path, null, parent);
}
/**
* Creates a {@code PathClassLoader} that operates on two given
* lists of files and directories. The entries of the first list
* should be one of the following:
*
*
* - Directories containing classes or resources.
*
- JAR/ZIP/APK files, possibly containing a "classes.dex" file.
*
- "classes.dex" files.
*
*
* The entries of the second list should be directories containing
* native library files. Both lists are separated using the
* character specified by the "path.separator" system property,
* which, on Android, defaults to ":".
*
* @param path
* the list of files and directories containing classes and
* resources
*
* @param libPath
* the list of directories containing native libraries
*
* @param parent
* the parent class loader
*/
public PathClassLoader(String path, String libPath, ClassLoader parent) {
super(parent);
.... 看到了PathClassLoader類也是繼承了ClassLoader的,但是他的構造函數和DexClassLoader有點區別就是,少了一個dexOutDir,這個原因也是很簡單,因為PathClassLoader是加載/data/app中的apk,而這部分的apk都會解壓釋放dex到指定的目錄:/data/dalvik-cache
這個釋放解壓操作是系統做的。所以PathClassLoader可以不需要這個參數的。
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD48cD7Jz8Pmv7TBy8v7w8fBvbXEx/ix8KOsz8LD5tTawLS/tNK7z8JBbmRyb2lk1tC1xLj31tbA4LzT1NjG97fWsfC809TYxMTQqcDgo7o8L3A+PHA+PC9wPjxwcmUgY2xhc3M9"brush:java;">package com.example.androiddemo;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("DEMO", "Context的類加載加載器:"+Context.class.getClassLoader());
Log.i("DEMO", "ListView的類加載器:"+ListView.class.getClassLoader());
Log.i("DEMO", "應用程序默認加載器:"+getClassLoader());
Log.i("DEMO", "系統類加載器:"+ClassLoader.getSystemClassLoader());
Log.i("DEMO", "系統類加載器和Context的類加載器是否相等:"+(Context.class.getClassLoader()==ClassLoader.getSystemClassLoader()));
Log.i("DEMO", "系統類加載器和應用程序默認加載器是否相等:"+(getClassLoader()==ClassLoader.getSystemClassLoader()));
Log.i("DEMO","打印應用程序默認加載器的委派機制:");
ClassLoader classLoader = getClassLoader();
while(classLoader != null){
Log.i("DEMO", "類加載器:"+classLoader);
classLoader = classLoader.getParent();
}
Log.i("DEMO","打印系統加載器的委派機制:");
classLoader = ClassLoader.getSystemClassLoader();
while(classLoader != null){
Log.i("DEMO", "類加載器:"+classLoader);
classLoader = classLoader.getParent();
}
}
}
依次來看一下
1) 系統類的加載器
Log.i("DEMO", "Context的類加載加載器:"+Context.class.getClassLoader());
Log.i("DEMO", "ListView的類加載器:"+ListView.class.getClassLoader());從結果看到他們的加載器是:BootClassLoader,關於他源碼我沒有找到,只找到了class文件(用jd-gui查看):看到他也是繼承了ClassLoader類。
2) 應用程序的默認加載器
Log.i("DEMO", "應用程序默認加載器:"+getClassLoader());運行結果:默認類加載器是PathClassLoader,同時可以看到加載的apk路徑,libPath(一般包括/vendor/lib和/system/lib)
3) 系統類加載器
Log.i("DEMO", "系統類加載器:"+ClassLoader.getSystemClassLoader());運行結果:4) 默認加載器的委派機制關系
Log.i("DEMO","打印應用程序默認加載器的委派機制:");
ClassLoader classLoader = getClassLoader();
while(classLoader != null){
Log.i("DEMO", "類加載器:"+classLoader);
classLoader = classLoader.getParent();
}打印結果:默認加載器PathClassLoader的父親是BootClassLoader
5) 系統加載器的委派機制關系
Log.i("DEMO","打印系統加載器的委派機制:");
classLoader = ClassLoader.getSystemClassLoader();
while(classLoader != null){
Log.i("DEMO", "類加載器:"+classLoader);
classLoader = classLoader.getParent();
}運行結果:
可以看到系統加載器的父親也是BootClassLoader
DexClassLoader加載原理和分析在實現插件時不同操作造成錯誤的原因分析
這裡主要用了三個工程:
PluginImpl:插件接口工程(只是接口的定義)
PluginSDK:插件工程(實現插件接口,定義具體的功能)
HostProject:宿主工程(需要引用插件接口工程,然後動態的加載插件工程)(例子項目中名字是PluginDemos)
下面來看一下源代碼:
1) IBean.java
package com.pluginsdk.interfaces;
public abstract interface IBean{
public abstract String getName();
public abstract void setName(String paramString);
}package com.pluginsdk.interfaces;
import android.content.Context;
public abstract interface IDynamic{
public abstract void methodWithCallBack(YKCallBack paramYKCallBack);
public abstract void showPluginWindow(Context paramContext);
public abstract void startPluginActivity(Context context,Class> cls);
public abstract String getStringForResId(Context context);
}其他的就不列舉了。1) Dynamic.java
/**
* Dynamic1.java
* com.youku.pluginsdk.imp
*
* Function: TODO
*
* ver date author
* ──────────────────────────────────
* 2014-10-20 Administrator
*
* Copyright (c) 2014, TNT All Rights Reserved.
*/
package com.pluginsdk.imp;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import com.pluginsdk.bean.Bean;
import com.pluginsdk.interfaces.IDynamic;
import com.pluginsdk.interfaces.YKCallBack;
import com.youku.pluginsdk.R;
/**
* ClassName:Dynamic1
*
* @author jiangwei
* @version
* @since Ver 1.1
* @Date 2014-10-20 下午5:57:10
*/
public class Dynamic implements IDynamic{
/**
*/
public void methodWithCallBack(YKCallBack callback) {
Bean bean = new Bean();
bean.setName("PLUGIN_SDK_USER");
callback.callback(bean);
}
public void showPluginWindow(Context context) {
AlertDialog.Builder builder = new Builder(context);
builder.setMessage("對話框");
builder.setTitle(R.string.hello_world);
builder.setNegativeButton("取消", new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
Dialog dialog = builder.create();//.show();
dialog.show();
}
public void startPluginActivity(Context context,Class> cls){
/**
*這裡要注意幾點:
*1、如果單純的寫一個MainActivity的話,在主工程中也有一個MainActivity,開啟的Activity還是主工程中的MainActivity
*2、如果這裡將MainActivity寫成全名的話,還是有問題,會報找不到這個Activity的錯誤
*/
Intent intent = new Intent(context,cls);
context.startActivity(intent);
}
public String getStringForResId(Context context){
return context.getResources().getString(R.string.hello_world);
}
}
/**
* User.java
* com.youku.pluginsdk.bean
*
* Function: TODO
*
* ver date author
* ──────────────────────────────────
* 2014-10-20 Administrator
*
* Copyright (c) 2014, TNT All Rights Reserved.
*/
package com.pluginsdk.bean;
/**
* ClassName:User
*
* @author jiangwei
* @version
* @since Ver 1.1
* @Date 2014-10-20 下午1:35:16
*/
public class Bean implements com.pluginsdk.interfaces.IBean{
/**
*
*/
private String name = "這是來自於插件工程中設置的初始化的名字";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1) MainActivity.java
package com.plugindemo;
import java.io.File;
import java.lang.reflect.Method;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.pluginsdk.interfaces.IBean;
import com.pluginsdk.interfaces.IDynamic;
import com.pluginsdk.interfaces.YKCallBack;
import com.youku.plugindemo.R;
import dalvik.system.DexClassLoader;
public class MainActivity extends Activity {
private AssetManager mAssetManager;//資源管理器
private Resources mResources;//資源
private Theme mTheme;//主題
private String apkFileName = "PluginSDKs.apk";
private String dexpath = null;//apk文件地址
private File fileRelease = null; //釋放目錄
private DexClassLoader classLoader = null;
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dexpath = Environment.getExternalStorageDirectory() + File.separator+apkFileName;
fileRelease = getDir("dex", 0);
/*初始化classloader
* dexpath dex文件地址
* fileRelease 文件釋放地址
* 父classLoader
*/
Log.d("DEMO", (getClassLoader()==ListView.class.getClassLoader())+"");
Log.d("DEMO",ListView.class.getClassLoader()+"");
Log.d("DEMO", Context.class.getClassLoader()+"");
Log.d("DEMO", Context.class.getClassLoader().getSystemClassLoader()+"");
Log.d("DEMO",Activity.class.getClassLoader()+"");
Log.d("DEMO", (Context.class.getClassLoader().getSystemClassLoader() == ClassLoader.getSystemClassLoader())+"");
Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");
classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),null,getClassLoader());
Button btn_1 = (Button)findViewById(R.id.btn_1);
Button btn_2 = (Button)findViewById(R.id.btn_2);
Button btn_3 = (Button)findViewById(R.id.btn_3);
Button btn_4 = (Button)findViewById(R.id.btn_4);
Button btn_5 = (Button)findViewById(R.id.btn_5);
Button btn_6 = (Button)findViewById(R.id.btn_6);
btn_1.setOnClickListener(new View.OnClickListener() {//普通調用 反射的方式
@Override
public void onClick(View arg0) {
Class mLoadClassBean;
try {
mLoadClassBean = classLoader.loadClass("com.pluginsdk.bean.Bean");
Object beanObject = mLoadClassBean.newInstance();
Log.d("DEMO", "ClassLoader:"+mLoadClassBean.getClassLoader());
Log.d("DEMO", "ClassLoader:"+mLoadClassBean.getClassLoader().getParent());
Method getNameMethod = mLoadClassBean.getMethod("getName");
getNameMethod.setAccessible(true);
String name = (String) getNameMethod.invoke(beanObject);
Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_2.setOnClickListener(new View.OnClickListener() {//帶參數調用
@Override
public void onClick(View arg0) {
Class mLoadClassBean;
try {
mLoadClassBean = classLoader.loadClass("com.pluginsdk.bean.Bean");
Object beanObject = mLoadClassBean.newInstance();
//接口形式調用
Log.d("DEMO", beanObject.getClass().getClassLoader()+"");
Log.d("DEMO",IBean.class.getClassLoader()+"");
Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");
IBean bean = (IBean)beanObject;
bean.setName("宿主程序設置的新名字");
Toast.makeText(MainActivity.this, bean.getName(), Toast.LENGTH_SHORT).show();
}catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_3.setOnClickListener(new View.OnClickListener() {//帶回調函數的調用
@Override
public void onClick(View arg0) {
Class mLoadClassDynamic;
try {
mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.imp.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
//接口形式調用
IDynamic dynamic = (IDynamic)dynamicObject;
//回調函數調用
YKCallBack callback = new YKCallBack() {//回調接口的定義
public void callback(IBean arg0) {
Toast.makeText(MainActivity.this, arg0.getName(), Toast.LENGTH_SHORT).show();
};
};
dynamic.methodWithCallBack(callback);
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_4.setOnClickListener(new View.OnClickListener() {//帶資源文件的調用
@Override
public void onClick(View arg0) {
loadResources();
Class mLoadClassDynamic;
try {
mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.imp.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
//接口形式調用
IDynamic dynamic = (IDynamic)dynamicObject;
dynamic.showPluginWindow(MainActivity.this);
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_5.setOnClickListener(new View.OnClickListener() {//帶資源文件的調用
@Override
public void onClick(View arg0) {
loadResources();
Class mLoadClassDynamic;
try {
mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.imp.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
//接口形式調用
IDynamic dynamic = (IDynamic)dynamicObject;
dynamic.startPluginActivity(MainActivity.this,
classLoader.loadClass("com.plugindemo.MainActivity"));
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
btn_6.setOnClickListener(new View.OnClickListener() {//帶資源文件的調用
@Override
public void onClick(View arg0) {
loadResources();
Class mLoadClassDynamic;
try {
mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.imp.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
//接口形式調用
IDynamic dynamic = (IDynamic)dynamicObject;
String content = dynamic.getStringForResId(MainActivity.this);
Toast.makeText(getApplicationContext(), content+"", Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e("DEMO", "msg:"+e.getMessage());
}
}
});
}
protected void loadResources() {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexpath);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();
superRes.getDisplayMetrics();
superRes.getConfiguration();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}
@Override
public Theme getTheme() {
return mTheme == null ? super.getTheme() : mTheme;
}
}
三個工程的下載地址:http://download.csdn.net/detail/jiangwei0910410003/8188011注意是lib文件夾,不是libs,這個是有區別的,後面會說道
項目引用完成之後,我們編譯PluginSDKs項目,生成PluginSDKs.apk放到手機的sdcard的根目錄(因為我代碼中是從這個目錄進行加載apk的,當然這個目錄是可以修改的),然後運行HostProject
看到效果了吧。運行成功,其實這個對話框是在插件中定義的,但是我們知道定義對話框是需要context變量的,所以這個變量就是通過參數從宿主工程中傳遞到插件工程即可,成功了就不能這麼了事,因為我還沒有說道我遇到的問題,下面就來看一下遇到的幾個問題
這個問題產生的操作:
插件工程PluginSDKs的引用方式不變,宿主工程PluginDemos的引用方式改變
在說這個原因之前先來了解一下Eclipse中引用工程的不同方式和區別:
第一種:最常用的將引用工程打成jar放到需要引用工程的libs下面(這裡是將PluginImpl打成jar,放到HostProject工程的libs中)
這種方式是Eclipse推薦使用的,當我們在建立一個項目的時候也會自動產生這個文件夾,當我們將我們需要引用的工程打成jar,然後放到這個文件夾之後,Eclipse就自動導入了(這個功能是Eclipse3.7之後有的)。
第二種:和第一種的區別是,我們可以從新新建一個文件夾比如是lib,然後將引用的jar放到這個文件夾中,但是此時Eclipse是不會自動導入的,需要我們手動的導入(add build path...),但是這個是一個區別,還有一個區別,也是到這個這個報錯原因的區別,就是libs文件夾中的jar,在運行的時候是會將這個jar集成到程序中的,而我們新建的文件夾(名字非libs即可),及時我們手動的導入,編譯是沒有問題的,但是運行的時候,是不會將jar集成到程序中。
第三種:和前兩種的區別是不需要將引用工程打成jar,直接引用這個工程
這種方式其實效果和第一種差不多,唯一的區別就是不需要打成jar,但是運行的時候是不會將引用工程集成到程序中的。
第四種:和第三種的方式是一樣的,也是不需要將引用工程打成jar,直接引用工程:
這個前提是需要設置PluginImpl項目為Library,同時引用的項目和被引用的項目必須在一個工作空間中,不然會報錯,這種的效果和第二種是一樣的,在運行的時候是會將引用工程集成到程序中的。
第五種:和第一種、第二種差不多,導入jar:
這裡有很多種方式選擇jar的位置,但是這些操作的效果和第一種是一樣的,運行的時候是不會將引用的jar集成到程序中的。
總結上面的五種方式,我們可以看到,第二種和第四種的效果是一樣的,也是最普遍的導入引用工程的方式,因為其他三種方式的話,其實在編譯的時候是不會有問題的,但是在運行的時候會報錯(找不到指定的類,可以依次嘗試一下),不過這三種方式只要一步就可以和那兩種方式實現的效果一樣了
只要設置導出的時候勾選上這個jar就可以了。那麼其實這五種方式都是可以的,性質和效果是一樣的。
說完了Eclipse中引用工程的各種方式以及區別之後,我們在回過頭來看一下,上面遇到的問題:Could not find class...
其實這個問題就簡單了,原因是:插件工程PluginSDKs使用的是lib文件夾導入的jar(這個jar是不會集成到程序中的),而宿主工程PluginDemos的引用工程的方式也變成了lib文件夾(jar也是不會集成到程序中的)。那麼程序運行的時候就會出現錯誤:
Could not find class "com.pluginsdk.interfaces.IBean'
這個問題產生的操作:
插件工程PluginSDKs和宿主工程PluginDemos引用工程的方式都變成library(或者是都用libs文件夾導入jar)
這個錯誤的原因也是很多做插件的開發者第一次都會遇到的問題,其實這個問題的本質是PluginImpl中的接口被加載了兩次,因為插件工程和宿主工程在運行的時候都會把PluginImpl集成到程序中。對於這個問題,我們來分析一下,首先對於宿主apk,他的類加載器是PathClassLoader(這個對於每個應用來說是默認的加載器,原因很簡單,PathClassLoader只能加載/data/app目錄下的apk,就是已經安裝的apk,一般我們的apk都是安裝之後在運行,所以用這個加載器也是理所當然的)。這個加載器開始加載插件接口工程(宿主工程中引入的PluginImpl)中的IBean。當使用DexClassLoader加載PluginSDKs.apk的時候,首先會讓宿主apk的PathClassLoader加載器去加載,這個好多人有點迷糊了,為什麼會先讓PathClassLoader加載器去加載呢?
這個就是Java中的類加載機制的雙親委派機制:http://blog.csdn.net/jiangwei0910410003/article/details/17733153
Android中的加載機制也是類似的,我們這裡的代碼設置了DexClassLoader的父加載器為當前類加載器(宿主apk的PathClassLoader),不行的話,可以打印一下getClassLoader()方法的返回結果看一下。
classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),null,getClassLoader());那麼加載器就是一樣的了(宿主apk的PathClassLoader),那麼就奇怪了,都是一個為什麼還有錯誤呢?查看系統源碼可以了解:
Resolve.c源碼(這個是在虛擬機dalvik中的):源碼下載地址為:http://blog.csdn.net/jiangwei0910410003/article/details/37988637
我們來看一下他的一個主要函數:
/*
* 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,需要使用不同的類加載器。
說白了就是,同一個類加載器從不同的dex文件中加載相同的class。所以上面是同一個類加載器PathClassLoader去加載(宿主apk和插件apk)來自不同的dex中的相同的類IBean。所以我們在做動態加載的時候都說過:不要把接口的jar一起打包成jar/dex/apk
這個問題產生的操作:
插件工程PluginSDKs和宿主工程都是用Library方式引用工程(或者是libs),同時將上面的一行代碼
classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),null,getClassLoader());
修改成:
classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),null,ClassLoader.getSystemClassLoader());就是將DexClassLoader的父加載器修改了一下:我們知道getClassLoader()獲取到的是應用的默認加載器PathClassLoader,而ClassLoader.getSystemClassLoader()是獲取系統類加載器,這樣修改之後會出現這樣的錯誤的原因是:插件工程和宿主工程都集成了PluginImpl,所以DexClassLoader在加載Bean的時候,首先會讓ClassLoader.getSystemClassLoader()類加載器(DexClassLoader的父加載器)去查找,因為Bean是實現了IBean接口,這時候ClassLoader.getSystemClassLoader就會從插件工程的apk中查找這個接口,結果沒找到,沒找到的話就讓DexClassLoader去找,結果在PluginSDKs.apk中找到了,就加載進來,同時宿主工程中也集成了插件接口PluginImpl,他使用PathClassLoader去宿主工程中去查找,結果也是查找到了,也加載進來了,但是在進行類型轉化的時候出現了錯誤:
IBean bean = (IBean)beanObject;原因說白了就是:同一個類,用不同的類加載器進行加載產生出來的對象是不同的,不能進行相互賦值,負責就會出現轉化異常。
總結
上面就說到了一些開發插件的過程中會遇到的一些問題,當我們知道這些問題之後,解決方法自然就會有了,
1) 為了避免Could not find class...,我們必須要集成PluginImpl,方式是使用Library或者是libs文件夾導入jar
(這裡要注意,因為我們運行的其實是宿主工程apk,所以宿主工程一定要集成PluginImpl,如果他不集成的話,即使插件工程apk集成了也還是沒有效果的)
2) 為了避免Class ref in pre-verified class resolved to unexpected implementation,我們在宿主工程和插件工程中只能集成一份PluginImpl,在結合上面的錯誤避免方式,可以得到正確的方式:
一定是宿主工程集成PluginImpl,插件工程一定不能集成PluginImpl。
(以後再制作插件的時候記住一句話就可以了,插件工程打包不能集成接口jar,宿主工程打包一定要集成接口jar)
關於第三個問題,其實在開發的過程中一般不會碰到,這裡說一下主要是為了馬上介紹Android中的類加載器的相關只是來做鋪墊的
(PS:問題都解決了,後續就要介紹插件的制作了~~)
AndroidStudio 使用AIDL
一直以來都認為AIDL的應用離我很遙遠,甚至不知道如何去用,也就懶得去學,之前的項目中也看到過aidl文件,只是懶得去看而已,現在感覺自己真的是無藥可救了,如果只止步於學
Android仿騰訊應用寶 應用市場,下載界面, 帶進度按鈕
最近做應用市場,需要用到,下載帶進度的顯示的按鈕,因此找了下其他大神做的,直接拿來改進,並且刪減掉大量沒用到的。分享下改進後的。 重新修改,當下載進度有進度的時候,自動顯
struts2的Action(四)
實現ActionAction是struts2應用的核心,開發中需要大量的Action類,並在struts.xml中配置Action。Action中包含了對用戶請求的處理邏
Android基礎第六篇(上)
1. 網頁源碼查看器網頁源碼查看器案例實現在EditText中輸入網址,點擊按鈕獲取,獲取到網頁源碼,顯示在TextView上。在IE浏覽器中,快捷鍵Shift+F12可