diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginConfig.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginConfig.java index 7498b805..a1775a59 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginConfig.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginConfig.java @@ -51,6 +51,8 @@ public final class RePluginConfig { private String hostVersionName = ""; private String hostBuildID = ""; + private boolean optimizeArtLoadDex = false; + /** * 获取插件回调方法。通常无需调用此方法。 * @@ -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; + } +} \ No newline at end of file diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/Dex2OatUtils.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/Dex2OatUtils.java new file mode 100644 index 00000000..4e4a9fb0 --- /dev/null +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/Dex2OatUtils.java @@ -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; + } +} \ No newline at end of file diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/InterpretDex2OatHelper.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/InterpretDex2OatHelper.java new file mode 100644 index 00000000..1935647e --- /dev/null +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/InterpretDex2OatHelper.java @@ -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 + *

+ * snippet from: Tinker's TinkerDexOptimizer.java (https://github.com/Tencent/tinker) + *

+ * 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 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. + } + } + } + }); + } + } +} \ No newline at end of file