Skip to content

Commit

Permalink
Merge pull request #435 from cundong/dev
Browse files Browse the repository at this point in the history
Optimize the plugin's loading speed on ART
  • Loading branch information
cundong authored Nov 27, 2017
2 parents b79a27f + 12a1c1f commit 4284a60
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public final class RePluginConfig {
private String hostVersionName = "";
private String hostBuildID = "";

private boolean optimizeArtLoadDex = false;

/**
* 获取插件回调方法。通常无需调用此方法。
*
Expand Down Expand Up @@ -337,4 +339,28 @@ private boolean checkAllowModify() {
}
return true;
}
}

/**
* 是否在Art上对首次加载插件速度做优化
*
* @return
*/
public boolean isOptimizeArtLoadDex() {
return optimizeArtLoadDex;
}

/**
* 是否在Art上对首次加载插件速度做优化,默认为false
*
* @param optimizeArtLoadDex
* @return
* @since 2.2.2
*/
public RePluginConfig setOptimizeArtLoadDex(boolean optimizeArtLoadDex) {
if (!checkAllowModify()) {
return this;
}
this.optimizeArtLoadDex = optimizeArtLoadDex;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* 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.utils;

import android.os.Build;
import android.util.Log;

import com.qihoo360.replugin.RePluginInternal;

import java.io.File;
import java.io.IOException;

/**
* @author RePlugin Team
*
* Art Dex2Oat utils
*/
public class Dex2OatUtils {

public static final String TAG = "Dex2Oat";

private static final boolean FOR_DEV = RePluginInternal.FOR_DEV;

/**
* 判断当前是否Art模式
*
* @return
*/
public static boolean isArtMode() {
return System.getProperty("java.vm.version", "").startsWith("2");
}

/**
* 在真正loadDex之前 inject
*
* @param dexPath
* @param optimizedDirectory
* @param optimizedFileName
* @return
*/
public static void injectLoadDex(String dexPath, String optimizedDirectory, String optimizedFileName) {
if (Dex2OatUtils.isArtMode()) {
File odexFile = new File(optimizedDirectory, optimizedFileName);

if (!odexFile.exists() || odexFile.length() <= 0) {

if (FOR_DEV) {
Log.d(Dex2OatUtils.TAG, optimizedFileName + " 文件不存在");
}

long being = System.currentTimeMillis();
boolean injectLoadDex = innerInjectLoadDex(dexPath, optimizedDirectory, optimizedFileName);

if (FOR_DEV) {
Log.d(Dex2OatUtils.TAG, "injectLoadDex use:" + (System.currentTimeMillis() - being));
Log.d(Dex2OatUtils.TAG, "injectLoadDex result:" + injectLoadDex);
}
} else {
if (FOR_DEV) {
Log.d(Dex2OatUtils.TAG, optimizedFileName + " 文件存在, 不需要inject,size:" + odexFile.length());
}
}
}
}

private static boolean innerInjectLoadDex(String dexPath, String optimizedDirectory, String optimizedFileName) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
if (FOR_DEV) {
Log.d(TAG, "before Android L, do nothing.");
}
return false;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
return injectLoadDex4Art(dexPath, optimizedDirectory, optimizedFileName);
} else {
return injectLoadDex4More();
}
}

private static boolean injectLoadDexBeforeN() {
if (isArtMode()) {

long begin = System.currentTimeMillis();

if (FOR_DEV) {
Log.d(TAG, "Art before Android N, try 2 hook.");
}

try {
// ArtAdapter.setDex2oatEnabledNative(true);
} catch (Throwable e) {
if (FOR_DEV) {
e.printStackTrace();
Log.e(TAG, "hook error");
}
}

if (FOR_DEV) {
Log.d(TAG, "hook end, use:" + (System.currentTimeMillis() - begin));
}

return true;
}

if (FOR_DEV) {
Log.d(TAG, "not Art, do nothing.");
}
return false;
}

private static boolean injectLoadDex4Art(String dexPath, String optimizedDirectory, String optimizedFileName) {
if (FOR_DEV) {
Log.d(TAG, "Andorid Art, try 2 interpretDex2Oat, interpret-only.");
}

String odexAbsolutePath = (optimizedDirectory + File.separator + optimizedFileName);

long begin = System.currentTimeMillis();
try {
InterpretDex2OatHelper.interpretDex2Oat(dexPath, odexAbsolutePath);
} catch (IOException e) {
if (FOR_DEV) {
e.printStackTrace();
Log.e(TAG, "interpretDex2Oat Error");
}
return false;
}

if (FOR_DEV) {
Log.d(TAG, "interpretDex2Oat use:" + (System.currentTimeMillis() - begin));
Log.d(TAG, "interpretDex2Oat odexSize:" + InterpretDex2OatHelper.getOdexSize(odexAbsolutePath));
}

return true;
}

private static boolean injectLoadDex4More() {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.qihoo360.replugin.utils;

import android.os.Build;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
* InterpretDex2OatHelper
* <p>
* snippet from: Tinker's TinkerDexOptimizer.java (https://github.com/Tencent/tinker)
* <p>
* Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstall apk.
*/
public class InterpretDex2OatHelper {

private static String getCurrentInstructionSet() throws Exception {

Class<?> clazz = Class.forName("dalvik.system.VMRuntime");
Method currentGet = clazz.getDeclaredMethod("getCurrentInstructionSet");

return (String) currentGet.invoke(null);
}

public static long getOdexSize(String oatFilePath) {
File file = new File(oatFilePath);
return file.exists() ? file.length() : -1;
}

public static void interpretDex2Oat(String dexFilePath, String oatFilePath) throws IOException {

String targetISA = null;
try {
targetISA = getCurrentInstructionSet();
} catch (Exception e) {
e.printStackTrace();
}

final File oatFile = new File(oatFilePath);
if (!oatFile.exists()) {
oatFile.getParentFile().mkdirs();
}

final List<String> commandAndParams = new ArrayList<>();
commandAndParams.add("dex2oat");

// for 7.1.1, duplicate class fix
if (Build.VERSION.SDK_INT >= 24) {
commandAndParams.add("--runtime-arg");
commandAndParams.add("-classpath");
commandAndParams.add("--runtime-arg");
commandAndParams.add("&");
}
commandAndParams.add("--dex-file=" + dexFilePath);
commandAndParams.add("--oat-file=" + oatFilePath);
commandAndParams.add("--instruction-set=" + targetISA);

if (Build.VERSION.SDK_INT > 25) {
commandAndParams.add("--compiler-filter=quicken");
} else {
commandAndParams.add("--compiler-filter=interpret-only");
}

final ProcessBuilder pb = new ProcessBuilder(commandAndParams);
pb.redirectErrorStream(true);

final Process dex2oatProcess = pb.start();

StreamConsumer.consumeInputStream(dex2oatProcess.getInputStream());
StreamConsumer.consumeInputStream(dex2oatProcess.getErrorStream());

try {
final int ret = dex2oatProcess.waitFor();

if (ret != 0) {
throw new IOException("dex2oat works unsuccessfully, exit code: " + ret);
}
} catch (InterruptedException e) {
throw new IOException("dex2oat is interrupted, msg: " + e.getMessage(), e);
}
}

private static class StreamConsumer {
static final Executor STREAM_CONSUMER = Executors.newSingleThreadExecutor();

static void consumeInputStream(final InputStream is) {
STREAM_CONSUMER.execute(new Runnable() {
@Override
public void run() {

if (is == null) {
return;
}

final byte[] buffer = new byte[256];
try {
while ((is.read(buffer)) > 0) {
// To satisfy checkstyle rules.
}
} catch (IOException ignored) {
// Ignored.
} finally {
try {
is.close();
} catch (Exception ignored) {
// Ignored.
}
}
}
});
}
}
}

0 comments on commit 4284a60

Please sign in to comment.