refactor support multidex feature in plugin application for the ROM below LOLLIPOP

This commit is contained in:
wangfuda 2017-08-06 20:27:31 +08:00
parent 2b67f455b4
commit 58ef28e5d1
13 changed files with 127 additions and 168 deletions

View File

@ -77,6 +77,16 @@ public class Constant {
*/
public static final String LOCAL_PLUGIN_APK_COVER_DIR = "p_c";
/**
* 插件Odex优化前释放的以插件名独立隔离的子目录
*/
public static final String LOCAL_PLUGIN_MULTI_DEX_SUB_DIR = "_md";
/**
* 插件Odex优化后释放的以插件名独立隔离的子目录
*/
public static final String LOCAL_PLUGIN_MULTI_ODEX_SUB_DIR = "_mod";
/**
* 插件文件名name-low-high-current.jar
* 插件文件名规范barcode-1-10-2.jar

View File

@ -19,6 +19,7 @@ package com.qihoo360.replugin;
import android.os.Build;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.utils.CloseableUtils;
import com.qihoo360.replugin.utils.FileUtils;
import com.qihoo360.replugin.utils.ReflectUtils;
@ -68,7 +69,7 @@ public class PluginDexClassLoader extends DexClassLoader {
public PluginDexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
installExtraDexes(dexPath, optimizedDirectory, parent);
installMultiDexesBeforeLollipop(dexPath, optimizedDirectory, parent);
mHostClassLoader = RePluginInternal.getAppClassLoader();
@ -146,28 +147,34 @@ public class PluginDexClassLoader extends DexClassLoader {
* @param dexPath
* @param optimizedDirectory
* @param parent
* @deprecated apply to ROM below Lollipop,may be deprecated
*/
private void installExtraDexes(String dexPath, String optimizedDirectory, ClassLoader parent) {
private void installMultiDexesBeforeLollipop(String dexPath, String optimizedDirectory, ClassLoader parent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return;
}
List<Object[]> allElements = new LinkedList<>();
try {
// get dexElements of main dex
Class<?> clz = Class.forName("dalvik.system.BaseDexClassLoader");
Object pathList = ReflectUtils.readField(clz, this, "pathList");
Object[] mainElements = (Object[]) ReflectUtils.readField(pathList.getClass(), pathList, "dexElements");
allElements.add(mainElements);
// get paths of dex
List<File> dexFiles = getDexFiles(dexPath);
// get dexElements of extra dex (need to load dex first)
if (dexFiles != null && dexFiles.size() > 0) {
List<Object[]> allElements = new LinkedList<>();
// get dexElements of main dex
Class<?> clz = Class.forName("dalvik.system.BaseDexClassLoader");
Object pathList = ReflectUtils.readField(clz, this, "pathList");
Object[] mainElements = (Object[]) ReflectUtils.readField(pathList.getClass(), pathList, "dexElements");
allElements.add(mainElements);
for (File file : dexFiles) {
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(TAG, "dex file:" + file.getName());
}
DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), optimizedDirectory, optimizedDirectory, parent);
Object obj = ReflectUtils.readField(clz, dexClassLoader, "pathList");
@ -180,14 +187,14 @@ public class PluginDexClassLoader extends DexClassLoader {
// rewrite Elements combined to classLoader
ReflectUtils.writeField(pathList.getClass(), pathList, "dexElements", combineElements);
}
//Test whether the Extra Dex is installed
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
//Test whether the Extra Dex is installed
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
Object object = ReflectUtils.readField(pathList.getClass(), pathList, "dexElements");
int length = Array.getLength(object);
LogDebug.d(TAG, "dexElements length:" + length);
Object object = ReflectUtils.readField(pathList.getClass(), pathList, "dexElements");
int length = Array.getLength(object);
LogDebug.d(TAG, "dexElements length:" + length);
}
}
} catch (Exception e) {
@ -236,12 +243,29 @@ public class PluginDexClassLoader extends DexClassLoader {
* @return the File list of the extra dexes
*/
private List<File> getDexFiles(String dexPath) {
PluginInfo pi = null;
ZipFile zipFile = null;
List<File> files = null;
try {
zipFile = new ZipFile(dexPath);
files = traverseZipFile(dexPath.substring(0, dexPath.lastIndexOf("/")), zipFile);
} catch (IOException e) {
String installedFileName = dexPath.substring(dexPath.lastIndexOf(File.separator) + 1).replace(".jar", "");
List<PluginInfo> piList = RePlugin.getPluginInfoList();
for (PluginInfo info : piList) {
if (info.makeInstalledFileName().equals(installedFileName)) {
pi = info;
break;
}
}
if (pi != null) {
zipFile = new ZipFile(dexPath);
files = traverseZipFile(pi.getUnoptDexParentDir().getAbsolutePath(), zipFile);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
CloseableUtils.closeQuietly(zipFile);

View File

@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@ -380,6 +381,34 @@ public class PluginInfo implements Parcelable, Cloneable {
return new File(dir, makeInstalledFileName() + ".jar");
}
/**
* 获取Dex优化前生成时所在的目录 <p>
* 若为"纯APK"插件则会位于app_p_od中若为"p-n"插件则会位于"app_plugins_v3_odex" <p>
* 若支持同版本覆盖安装的话则会位于app_p_c中 <p>
* 注意仅供框架内部使用
*
* @return 优化前Dex所在目录的File对象
*/
public File getUnoptDexParentDir() {
File dir;
// 必须使用宿主的Context对象防止出现目录定位到插件内的问题
Context context = RePluginInternal.getAppContext();
if (isPnPlugin()) {
dir = context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0);
} else if (getIsPendingCover()) {
dir = context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0);
} else {
dir = context.getDir(Constant.LOCAL_PLUGIN_APK_ODEX_SUB_DIR, 0);
}
File subDir = new File(dir + File.separator + makeInstalledFileName() + Constant.LOCAL_PLUGIN_MULTI_DEX_SUB_DIR);
if (!subDir.exists()) {
subDir.mkdir();
}
return subDir;
}
/**
* 获取Dex优化后生成时所在的目录 <p>
* 若为"纯APK"插件则会位于app_p_od中若为"p-n"插件则会位于"app_plugins_v3_odex" <p>
@ -389,15 +418,27 @@ public class PluginInfo implements Parcelable, Cloneable {
* @return 优化后Dex所在目录的File对象
*/
public File getDexParentDir() {
File dir;
// 必须使用宿主的Context对象防止出现目录定位到插件内的问题
Context context = RePluginInternal.getAppContext();
if (isPnPlugin()) {
return context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0);
dir = context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0);
} else if (getIsPendingCover()) {
return context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0);
dir = context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0);
} else {
return context.getDir(Constant.LOCAL_PLUGIN_APK_ODEX_SUB_DIR, 0);
dir = context.getDir(Constant.LOCAL_PLUGIN_APK_ODEX_SUB_DIR, 0);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
File subDir = new File(dir + File.separator + makeInstalledFileName() + Constant.LOCAL_PLUGIN_MULTI_ODEX_SUB_DIR);
if (!subDir.exists()) {
subDir.mkdir();
}
dir = subDir;
}
return dir;
}
/**

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.text.TextUtils;
@ -437,7 +438,10 @@ public class PluginManagerServer {
private void delete(@NonNull PluginInfo pi) {
try {
FileUtils.forceDelete(new File(pi.getPath()));
FileUtils.forceDelete(pi.getDexFile());
FileUtils.forceDelete(pi.getDexParentDir());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
FileUtils.forceDelete(pi.getUnoptDexParentDir());
}
FileUtils.forceDelete(pi.getNativeLibsDir());
} catch (IOException e) {
if (LogRelease.LOGR) {

View File

@ -18,6 +18,7 @@
package com.qihoo360.replugin.utils.pkg;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
@ -280,7 +281,10 @@ public class PackageFilesUtil {
}
try {
FileUtils.forceDelete(info.getApkFile());
FileUtils.forceDelete(info.getDexFile());
FileUtils.forceDelete(info.getDexParentDir());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
FileUtils.forceDelete(info.getUnoptDexParentDir());
}
FileUtils.forceDelete(info.getNativeLibsDir());
} catch (IOException e) {
if (LogRelease.LOGR) {

View File

@ -139,6 +139,5 @@
android:authorities="com.qihoo360.replugin.sample.demo1.provider2" />
<activity android:name=".activity.for_result.ForResultActivity"/>
<activity android:name=".testcase.TestCaseActivity"/>
</application>
</manifest>

View File

@ -47,7 +47,6 @@ import com.qihoo360.replugin.sample.demo1.activity.theme.ThemeBlackNoTitleBarAct
import com.qihoo360.replugin.sample.demo1.activity.theme.ThemeBlackNoTitleBarFullscreenActivity;
import com.qihoo360.replugin.sample.demo1.activity.theme.ThemeDialogActivity;
import com.qihoo360.replugin.sample.demo1.service.PluginDemoService1;
import com.qihoo360.replugin.sample.demo1.testcase.TestCaseActivity;
import com.qihoo360.replugin.sample.demo2.IDemo2;
import com.qihoo360.replugin.sample.library.LibMainActivity;
@ -318,17 +317,6 @@ public class MainActivity extends Activity {
v.getContext().startActivity(intent);
}
}));
// =========
// test case
// =========
mItems.add(new TestItem("TEST CASE For RePlugin", new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(v.getContext(), TestCaseActivity.class);
v.getContext().startActivity(intent);
}
}));
}
private static final int REQUEST_CODE_DEMO2 = 0x021;

View File

@ -1,97 +0,0 @@
/*
* Copyright (C) 2005-2017 Qihoo 360 Inc.
*
* 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 com.qihoo360.replugin.sample.demo1.testcase;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.qihoo360.replugin.sample.demo1.R;
import com.qihoo360.replugin.sample.demo1.TestItem;
import java.util.ArrayList;
import java.util.List;
/**
* Created by osan on 2017/7/30.
*/
public class TestCaseActivity extends Activity {
private List<TestItem> mItems = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_testcase);
initData();
ListView lv = (ListView) findViewById(R.id.list_view);
lv.setAdapter(new TestAdapter());
}
private void initData() {
// =========
// TestMultiDex
// =========
mItems.add(new TestItem("Test access MultiDex method", new View.OnClickListener() {
@Override
public void onClick(View v) {
String ret = TestMultiDex.accessMultiDexMethod();
Toast.makeText(TestCaseActivity.this, ret, Toast.LENGTH_SHORT).show();
}
}));
}
private class TestAdapter extends BaseAdapter {
@Override
public int getCount() {
return mItems.size();
}
@Override
public TestItem getItem(int position) {
return mItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = new Button(TestCaseActivity.this);
}
TestItem item = getItem(position);
((Button) convertView).setText(item.title);
convertView.setOnClickListener(item.mClickListener);
return convertView;
}
}
}

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2005-2017 Qihoo 360 Inc.
~
~ 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>

View File

@ -31,6 +31,7 @@ android {
targetSdkVersion 21
applicationId "com.qihoo360.replugin.sample.demo2"
minSdkVersion 15
multiDexEnabled false
}
buildTypes {
@ -67,3 +68,7 @@ dependencies {
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.qihoo360.replugin:replugin-plugin-lib:2.1.6'
}
if (android.defaultConfig.multiDexEnabled) {
apply from: 'case.gradle'
}

View File

@ -0,0 +1,13 @@
dependencies {
if (android.defaultConfig.multiDexEnabled){
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.google.code.gson:gson:2.6.2'
compile 'com.squareup.pagerduty:pagerduty-incidents:2.0.0'
compile 'com.google.android.gms:play-services:9.6.1'
}
}

View File

@ -1,11 +1,9 @@
package com.qihoo360.replugin.sample.demo1.testcase;
package com.qihoo360.replugin.sample.demo2.testcase;
/**
* Created by osan on 2017/7/30.
*/
import android.util.Log;
/**
* multidex测试用例类
* 用于测试RP框架对运行在android rom 5.0以下版本的插件含多dex的multidex支持情况

View File

@ -27,7 +27,7 @@
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx1536M
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects