動態載入apk檔案並呼叫其程式碼
阿新 • • 發佈:2019-01-28
編寫一個apk,用於匯出方法,供其他apk呼叫
public class Dynamic implements IDynamic{
private Activity mActivity;
public Dynamic() {
// TODO Auto-generated constructor stub
}
@Override
public void init(Activity activity) {
mActivity = activity;
Log.i("TestDynamic" , "In Dynamic::Init");
}
@Override
public void showBanner() {
Toast.makeText(mActivity, "我是ShowBannber方法", 1500).show();
}
@Override
public void showDialog() {
Toast.makeText(mActivity, "我是ShowDialog方法", 1500).show();
}
@Override
public void showFullScreen() {
Toast.makeText(mActivity, "我是ShowFullScreen方法", 1500).show();
}
@Override
public void showAppWall() {
Toast.makeText(mActivity, "我是ShowAppWall方法", 1500).show();
}
@Override
public void destory() {
}
}
IDynamic是一個介面,用於外界可以使用介面進行呼叫
public interface IDynamic {
/**初始化方法*/
public void init(Activity activity);
/**自定義方法*/
public void showBanner();
public void showDialog();
public void showFullScreen();
public void showAppWall();
/**銷燬方法*/
public void destory();
}
此外,在manifest.xml中,需要宣告一個Action,用於其他apk能通過Intent檢索到此apk
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="com.example.dynamiclib"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
,名字無所謂,只要其他apk通過Intent能檢索到此apk就可以
二、呼叫動態apk
根據目標apk包名,獲取其包路徑、catche路徑等資訊,建立DexClassLoader物件,並通過反射機制來呼叫動態apk中的功能函式程式碼
public class MainActivity extends Activity implements OnClickListener{
private Button btn_show_banner;
private Button btn_show_dialog;
private Button btn_show_fullscreen;
private Button btn_show_appwall;
private final static String TAG = "TestDynamic";
private IDynamic libInterface = null;
private Method method_init = null;
private Method method_showBanner = null;
private Method method_showDialog = null;
private Method method_showFullScreen = null;
private Method method_showAppWall = null;
private Object object = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_show_banner = (Button) findViewById(R.id.button1);
btn_show_dialog = (Button) findViewById(R.id.button2);
btn_show_fullscreen = (Button) findViewById(R.id.button3);
btn_show_appwall = (Button) findViewById(R.id.button4);
/**
* 呼叫靜態apk時,需要有一個解壓路徑
* **/
File dexOutputDir = this.getDir("dex", 0);
/**
* 獲取apk存放的路徑,此apk未安裝,在磁碟上,也可能是網路臨時拉下來的
* 這裡測試,放在SD卡根目錄
* 主要是用於DexClassLoader的第一個引數使用
* **/
String dexPath = Environment.getExternalStorageDirectory().toString()
+ File.separator
+ "DynamicLib.apk";
Log.i(TAG, "ApkPath = "+dexPath);
Log.i(TAG, "dexOutPath = "+dexOutputDir.getAbsolutePath());
/**
* 與被呼叫的apk里名稱約定要一致
* 用來找到指定apk,系統中多個時,下面get要注意取哪一個
* **/
Intent intent = new Intent("com.example.dynamiclib");
/**
* 獲取包管理器
* **/
PackageManager pm = getPackageManager();
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
/**
* 獲得指定Activity資訊
* 如果有兩個名字都是“com.example.dynamiclib”的Intent,
* 則可能需要get(1),因為它會返回兩個activity
* **/
ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
/**
* 獲得apk的目錄或者jar的目錄
* **/
String apkPath = activityInfo.applicationInfo.sourceDir;
/**
* native程式碼的目錄
* **/
String libPath = activityInfo.applicationInfo.nativeLibraryDir;
/**
* 第一種方法
* 建立DexClassLoader
* 把apk載入到虛擬機器中
* 注意:這裡的路徑可以使用兩種
* 即可以是dexPath(磁碟上的靜態檔案)
* 也可以是通過pm獲取的已經安裝的apk的路徑
* **/
DexClassLoader dcl = new DexClassLoader(dexPath,
dexOutputDir.getAbsolutePath(),
null,
this.getClass().getClassLoader());
/**
* 第二種方法
* 使用PathClassLoader
* 這裡只能找到已經安裝的apk
* 並且路徑,必須通過Intent、PM來聯合獲取,否則不能成功
* 此外,Intent裡的名字,如果系統裡存在兩個名字相同的apk,name可能還需要試試第二個
* 因為不一定哪個在前,哪個在後
* **/
//PathClassLoader dcl = new PathClassLoader(apkPath, libPath, this.getClass().getClassLoader());
Log.i(TAG, "DexClassLoader ="+dcl);
try {
Log.i(TAG, "before dcl.loadClass");
/**
* 載入目標class,名稱為目標類名
* **/
Class<?> orgClass = dcl.loadClass("com.example.dynamiclib.Dynamic");
Log.i(TAG, "orgClass load is " + orgClass.getClassLoader().toString());
object = orgClass.newInstance();
Class[] param = new Class[1];
param[0] = Activity.class;
method_init = orgClass.getMethod("init", param);
method_showBanner = orgClass.getMethod("showBanner");
method_showDialog = orgClass.getMethod("showDialog");
method_showFullScreen = orgClass.getMethod("showFullScreen");
method_showAppWall = orgClass.getMethod("showAppWall");
if (method_init != null) {
method_init.invoke(object, this);
}
Log.i(TAG, "libInterFace is "+libInterface);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
Log.i(TAG, "Not Found Class");
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
btn_show_banner.setOnClickListener(this);
btn_show_dialog.setOnClickListener(this);
btn_show_fullscreen.setOnClickListener(this);
btn_show_appwall.setOnClickListener(this);
}
void ShowTips(String tips)
{
Toast.makeText(this, tips, Toast.LENGTH_LONG).show();
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.button1:
if (method_showBanner != null) {
try {
method_showBanner.invoke(object);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
ShowTips("類載入失敗");
}
break;
case R.id.button2:
if (method_showDialog != null) {
try {
method_showDialog.invoke(object);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
ShowTips("類載入失敗");
}
break;
case R.id.button3:
if (method_showFullScreen != null) {
try {
method_showFullScreen.invoke(object);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
ShowTips("類載入失敗");
}
break;
case R.id.button4:
if (method_showAppWall != null) {
try {
method_showAppWall.invoke(object);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
ShowTips("類載入失敗");
}
break;
default:
break;
}
}
}
- 這裡是此處通過反射機制來呼叫程式碼,還沒有完成通過介面來呼叫的功能,待繼續除錯
- 這裡是從已經安裝的apk獲取路徑等資訊,如果是目錄下靜態的apk(並沒有安裝到手機上),直接把構建DexClassLoader的第一個引數改為靜態apk的絕對路徑,其他不需要變化,即可正常執行;
- 不過如果使用PathClassLoader,來載入,必須通過intent查詢目標apk,同時目標apk必須已經安裝,因為它只能載入固定目錄下的apk,不能隨意路徑的apk
- 使用了IDynamic後,需要把對應的jar包引入到工程裡的lib中,通過“Build Path”->”Configure Build Path”裡進行新增;只把jar拷貝到這個目錄是沒有用的