diff --git a/README.md b/README.md index c73be7b0..7e87ac33 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,8 @@ If you **have any question, please [click here to read FAQ(Chinese Version)](htt JieLeMa - - + + diff --git a/README_CN.md b/README_CN.md index a66412f5..c53ac141 100644 --- a/README_CN.md +++ b/README_CN.md @@ -75,7 +75,7 @@ RePlugin的使用方法非常简单,大部分情况下和“单品”开发无 若您是**第一次接触RePlugin,则[请点击这里阅读《快速上手》](https://github.com/Qihoo360/RePlugin/wiki/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B)**,跟随我们的指引,了解更多的内容。 -若您想**了解更多有关RePlugin的玩法,则[请点击这里阅读《详细教程》](https://github.com/Qihoo360/RePlugin/wiki/%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B),**了解更多好玩的玩法。 +若您想**了解更多有关RePlugin的玩法,则[请点击这里阅读《详细教程》](https://github.com/Qihoo360/RePlugin/wiki/%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B)**,了解更多好玩的玩法。 若您想**看下RePlugin的Sample工程,进而了解框架的具体用法,则[请点击这里查看Sample源代码](https://github.com/Qihoo360/RePlugin/blob/master/replugin-sample)**。 @@ -118,8 +118,8 @@ RePlugin的使用方法非常简单,大部分情况下和“单品”开发无        借了吗 - - + + diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Loader.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Loader.java index 8d288d70..622d4a3e 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Loader.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Loader.java @@ -601,7 +601,11 @@ private HashMap getConfigProcessMap(ApplicationInfo appInfo) { private void adjustPluginProcess(ApplicationInfo appInfo) { HashMap processMap = getConfigProcessMap(appInfo); if (processMap == null || processMap.isEmpty()) { - processMap = genDynamicProcessMap(); + + PluginInfo pi = MP.getPlugin(mPluginName, false); + if (pi != null && pi.getFrameworkVersion() >= 4) { + processMap = genDynamicProcessMap(); + } } if (LOG) { diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java index 2d13001f..d5041940 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java @@ -25,6 +25,7 @@ import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.IBinder; +import android.os.Parcelable; import android.os.RemoteException; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; @@ -229,6 +230,9 @@ static final void cleanIntentPluginParams(Intent intent) { } void init() { + + RePlugin.getConfig().getCallbacks().initPnPluginOverride(); + if (HostConfigHelper.PERSISTENT_ENABLE) { // (默认)“常驻进程”作为插件管理进程,则常驻进程作为Server,其余进程作为Client if (IPC.isPersistentProcess()) { @@ -381,12 +385,47 @@ private final void refreshPluginMap(List plugins) { } } + /** + * 把插件Add到插件列表 + * + * @param info 待add插件的PluginInfo对象 + * @param plugin 待add插件的Plugin对象 + */ private void putPluginObject(PluginInfo info, Plugin plugin) { - // 同时加入PackageName和Alias(如有) - mPlugins.put(info.getPackageName(), plugin); - if (!TextUtils.isEmpty(info.getAlias())) { - // 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已 - mPlugins.put(info.getAlias(), plugin); + if (mPlugins.containsKey(info.getAlias()) || mPlugins.containsKey(info.getPackageName())) { + if (LOG) { + LogDebug.d(PLUGIN_TAG, "当前内置插件列表中已经有" + info.getName() + ",需要看看谁的版本号大。"); + } + + // 找到已经存在的 + Plugin existedPlugin = mPlugins.get(info.getPackageName()); + if (existedPlugin == null) { + existedPlugin = mPlugins.get(info.getAlias()); + } + + if (existedPlugin.mInfo.getVersion() < info.getVersion()) { + if (LOG) { + LogDebug.d(PLUGIN_TAG, "新传入的纯APK插件, name=" + info.getName() + ", 版本号比较大,ver=" + info.getVersion() + ",以TA为准。"); + } + + // 同时加入PackageName和Alias(如有) + mPlugins.put(info.getPackageName(), plugin); + if (!TextUtils.isEmpty(info.getAlias())) { + // 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已 + mPlugins.put(info.getAlias(), plugin); + } + } else { + if (LOG) { + LogDebug.d(PLUGIN_TAG, "新传入的纯APK插件" + info.getName() + "版本号还没有内置的大,什么都不做。"); + } + } + } else { + // 同时加入PackageName和Alias(如有) + mPlugins.put(info.getPackageName(), plugin); + if (!TextUtils.isEmpty(info.getAlias())) { + // 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已 + mPlugins.put(info.getAlias(), plugin); + } } } @@ -1133,7 +1172,7 @@ final void newPluginFound(PluginInfo info, boolean persistNeedRestart) { // 通知本进程:通知给外部使用者 Intent intent = new Intent(RePluginConstants.ACTION_NEW_PLUGIN); - intent.putExtra(RePluginConstants.KEY_PLUGIN_INFO, info); + intent.putExtra(RePluginConstants.KEY_PLUGIN_INFO, (Parcelable) info); intent.putExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, persistNeedRestart); intent.putExtra(RePluginConstants.KEY_SELF_NEED_RESTART, mNeedRestart); LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent); diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmHostSvc.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmHostSvc.java index 5b615532..1e5e0654 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmHostSvc.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmHostSvc.java @@ -23,6 +23,7 @@ import android.content.pm.ActivityInfo; import android.os.Binder; import android.os.IBinder; +import android.os.Parcelable; import android.os.RemoteException; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; @@ -256,6 +257,10 @@ public void regReceiver(String plugin, Map rcvFilMap) throws RemoteException { return; } + if (rcvFilMap == null) { + return; + } + HashMap> receiverFilterMap = (HashMap>) rcvFilMap; // 遍历此插件中所有静态声明的 Receiver @@ -378,7 +383,7 @@ private void syncInstalledPluginInfo2All(PluginInfo pi) { // 通知其它进程去更新 Intent intent = new Intent(PmBase.ACTION_NEW_PLUGIN); intent.putExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, mNeedRestart); - intent.putExtra("obj", needToSyncPi); + intent.putExtra("obj", (Parcelable) needToSyncPi); IPC.sendLocalBroadcast2AllSync(mContext, intent); if (LOG) { @@ -394,7 +399,7 @@ private void syncUninstalledPluginInfo2All(PluginInfo pi) { // 给各进程发送广播,同步更新 final Intent intent = new Intent(PluginInfoUpdater.ACTION_UNINSTALL_PLUGIN); - intent.putExtra("obj", pi); + intent.putExtra("obj", (Parcelable) pi); // 注意:若在attachBaseContext中调用此方法,则由于此时getApplicationContext为空,导致发送广播时会出现空指针异常。 // 则应该Post一下,待getApplicationContext有值后再发送广播。 if (RePluginInternal.getAppContext().getApplicationContext() != null) { @@ -459,7 +464,7 @@ public boolean pluginExtracted(String path) throws RemoteException { // 通知其它进程 Intent intent = new Intent(PmBase.ACTION_NEW_PLUGIN); intent.putExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, mNeedRestart); - intent.putExtra("obj", info); + intent.putExtra("obj", (Parcelable) info); IPC.sendLocalBroadcast2AllSync(mContext, intent); return true; diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java index 39961904..5e155c4c 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java @@ -167,4 +167,14 @@ public boolean isPluginBlocked(PluginInfo pluginInfo) { // Nothing, allow all return false; } -} + + /** + * 为了p-n插件初始化PluginOverride逻辑,只有老插件方案使用 + * 可以通过该回调,在进程初始化时,设置插件的override逻辑(每个进程都会调到) + * + * @since 2.2.2 + */ + public void initPnPluginOverride() { + // default, do Nothing + } +} \ No newline at end of file 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 1ded4ac4..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 @@ -48,6 +48,11 @@ public final class RePluginConfig { private boolean printDetailLog = false; private int defaultFrameworkVersion = 4; + private String hostVersionName = ""; + private String hostBuildID = ""; + + private boolean optimizeArtLoadDex = false; + /** * 获取插件回调方法。通常无需调用此方法。 * @@ -199,6 +204,66 @@ public RePluginConfig setMoveFileWhenInstalling(boolean moveFileWhenInstalling) return this; } + /** + * 获取宿主的 BuildID + * + * @return 宿主的BuildID + * @since 2.2.2 + */ + public String getHostBuildID() { + return hostBuildID; + } + + /** + * 设置宿主的 BuildID

+ * BuildID 是一个比 VersionName 和 VersionCode 更细的维度(例如:服务器每 build 一次,版本号加 1) + * + * @param buildID 宿主的BuildID + * @return RePluginConfig自己。这样可以连环调用set方法 + * @since 2.2.2 + */ + public RePluginConfig setHostBuild(String buildID) { + if (!checkAllowModify()) { + return this; + } + hostBuildID = buildID; + return this; + } + + /** + * 获取宿主的 VersionName + * + * @since 2.2.2 + */ + public String getHostVersionName() { + return hostVersionName; + } + + /** + * 设置宿主的 VersionName + * + * @param versionName 宿主的VersionName + * @return RePluginConfig自己。这样可以连环调用set方法 + * @since 2.2.2 + */ + public RePluginConfig setHostVersionName(String versionName) { + if (!checkAllowModify()) { + return this; + } + hostVersionName = versionName; + return this; + } + + /** + * 获取宿主的VersionBuild号 + * + * @return + * @since 2.2.2 + */ + public String getHostVersionBuild() { + return RePlugin.getConfig().getHostVersionName() + "." + RePlugin.getConfig().getHostBuildID(); + } + /** * 是否打印更详细的日志? * @@ -274,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/component/service/server/PluginServiceServer.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/PluginServiceServer.java index 37ca2230..133e85b1 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/PluginServiceServer.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/PluginServiceServer.java @@ -105,14 +105,13 @@ public void handleMessage(Message msg) { Bundle data = msg.getData(); Intent intent = data.getParcelable("intent"); - if (intent != null) { - ServiceRecord sr = retrieveServiceLocked(intent); - if (sr != null) { - sr.service.onStartCommand(intent, 0, 0); - } else { - if (LOG) { - LogDebug.e(PLUGIN_TAG, "pss.onStartCommand fail."); - } + ServiceRecord sr = (ServiceRecord)msg.obj; + + if (intent != null && sr != null) { + sr.service.onStartCommand(intent, 0, 0); + }else{ + if (LOG) { + LogDebug.e(PLUGIN_TAG, "pss.onStartCommand fail."); } } break; @@ -152,7 +151,7 @@ ComponentName startServiceLocked(Intent intent, Messenger client) { Bundle data = new Bundle(); data.putParcelable("intent", intent); message.setData(data); - + message.obj = sr; mHandler.sendMessage(message); return cn; diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/ServiceRecord.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/ServiceRecord.java index 8cb25c4a..dbdbca4b 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/ServiceRecord.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/ServiceRecord.java @@ -30,7 +30,7 @@ /** * 用来表示一个Service对象的信息 * 包含绑定此Service的连接信息、Intent绑定信息等 - * + *

* NOTE 类似于Android的ServiceRecord * * @author RePlugin Team @@ -110,7 +110,7 @@ public boolean hasAutoCreateConnections() { @Override public String toString() { - return "[srv=" + service.getClass().getName() + "; startRequested=" + startRequested + "; bindings=(" + bindings.size() + ") " + bindings + "]"; + return "[srv=" + service == null ? "null" : service.getClass().getName() + "; startRequested=" + startRequested + "; bindings=(" + bindings.size() + ") " + bindings + "]"; } public String getPlugin() { diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java index 43a58105..53e070d3 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java @@ -43,7 +43,13 @@ import org.json.JSONException; import org.json.JSONObject; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.util.Arrays; import java.util.Comparator; import java.util.regex.MatchResult; @@ -60,7 +66,9 @@ * @author RePlugin Team */ -public class PluginInfo implements Parcelable, Cloneable { +public class PluginInfo implements Serializable, Parcelable, Cloneable { + + private static final long serialVersionUID = -6531475023210445876L; private static final String TAG = "PluginInfo"; @@ -102,7 +110,8 @@ public class PluginInfo implements Parcelable, Cloneable { */ public static final int FRAMEWORK_VERSION_UNKNOWN = 0; - private JSONObject mJson; + private transient JSONObject mJson; + private String mJsonText; // 若插件需要更新,则会有此值 private PluginInfo mPendingUpdate; @@ -152,28 +161,6 @@ private PluginInfo(String pkgName, String alias, int low, int high, int version, setType(type); } - /** - * 将PluginInfo对象应用到此对象中(克隆) - * - * @param pi PluginInfo对象 - */ - public PluginInfo(PluginInfo pi) { - this.mJson = JSONHelper.cloneNoThrows(pi.mJson); - if (pi.mPendingUpdate != null) { - this.mPendingUpdate = new PluginInfo(pi.mPendingUpdate); - } - if (pi.mPendingDelete != null) { - this.mPendingDelete = new PluginInfo(pi.mPendingDelete); - } - this.mIsPendingCover = pi.mIsPendingCover; - if (pi.mPendingCover != null) { - this.mPendingCover = new PluginInfo(pi.mPendingCover); - } - if (pi.mParentInfo != null) { - this.mParentInfo = new PluginInfo(pi.mParentInfo); - } - } - private void initPluginInfo(JSONObject jo) { mJson = jo; @@ -840,7 +827,35 @@ private PluginInfo(Parcel source) { @Override public Object clone() { - return new PluginInfo(this); + PluginInfo pluginInfo = null; + + // 通过 transient 和 Serializable 配合实现深拷贝 + this.mJsonText = this.mJson != null ? this.mJson.toString() : null; + + // Object -> Stream -> clone Object + try { + ByteArrayOutputStream byteArrOut = new ByteArrayOutputStream(); + ObjectOutputStream objOut = new ObjectOutputStream(byteArrOut); + + objOut.writeObject(this); + + ByteArrayInputStream byteArrIn = new ByteArrayInputStream(byteArrOut.toByteArray()); + ObjectInputStream objIn = new ObjectInputStream(byteArrIn); + + pluginInfo = (PluginInfo) objIn.readObject(); + + if (pluginInfo != null && !TextUtils.isEmpty(this.mJsonText)) { + pluginInfo.mJson = new JSONObject(this.mJsonText); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (JSONException e) { + e.printStackTrace(); + } + + return pluginInfo; } @Override @@ -939,7 +954,7 @@ public boolean equals(Object obj) { PluginInfo pluginInfo = (PluginInfo) obj; try { - return pluginInfo.mJson.equals(mJson); + return pluginInfo.mJson.toString().equals(mJson.toString()); } catch (Exception e) { return false; } diff --git a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java index 6f89dd4d..3e724d23 100644 --- a/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java +++ b/replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java @@ -17,7 +17,6 @@ package com.qihoo360.replugin.packages; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; @@ -28,11 +27,9 @@ import com.qihoo360.loader2.CertUtils; import com.qihoo360.loader2.MP; import com.qihoo360.loader2.PluginNativeLibsHelper; -import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginEventCallbacks; import com.qihoo360.replugin.RePluginInternal; -import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; @@ -61,6 +58,12 @@ public class PluginManagerServer { private static final String TAG = "PluginManagerServer"; + /*** 非法类型的插件*/ + public static final int PLUGIN_TYPE_INVALID = 0; + /*** PN类型的插件*/ + public static final int PLUGIN_TYPE_PN = 1; + /*** APK类型的插件*/ + public static final int PLUGIN_TYPE_APK = 2; private static final byte[] LOCKER_PROCESS_KILLED = new byte[0]; private static final byte[] LOCKER = new byte[0]; @@ -161,7 +164,7 @@ private PluginInfo installLocked(String path) { if (checkResult < 0) { RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.VERIFY_VER_FAIL); return null; - } else if (checkResult == 0){ + } else if (checkResult == 0) { instPli.setIsPendingCover(true); } } @@ -207,7 +210,7 @@ private boolean verifySignature(PackageInfo pi, String path) { private int checkVersion(PluginInfo instPli, PluginInfo curPli) { // 支持插件同版本覆盖安装? // 若现在要安装的,与之前的版本相同,则覆盖掉之前的版本; - if (instPli.getVersion() == curPli.getVersion()) { + if (instPli.getVersion() == curPli.getVersion() && getPluginType(instPli) == getPluginType(curPli)) { if (LogDebug.LOG) { LogDebug.d(TAG, "isSameVersion: same version. " + "inst_ver=" + instPli.getVersion() + "; cur_ver=" + curPli.getVersion()); @@ -296,7 +299,8 @@ private void updateOrLater(PluginInfo curPli, PluginInfo instPli) { if (LogDebug.LOG) { LogDebug.w(TAG, "updateOrLater: Plugin is running. Later. pn=" + curPli.getName()); } - if (instPli.getVersion() > curPli.getVersion()) { + if (instPli.getVersion() > curPli.getVersion() || + instPli.getVersion() == curPli.getVersion() && getPluginType(instPli) != getPluginType(curPli)) { // 高版本升级 curPli.setPendingUpdate(instPli); curPli.setPendingDelete(null); @@ -304,7 +308,7 @@ private void updateOrLater(PluginInfo curPli, PluginInfo instPli) { if (LogDebug.LOG) { LogDebug.w(TAG, "updateOrLater: Plugin need update high version. clear PendingDelete and PendingCover."); } - } else if (instPli.getVersion() == curPli.getVersion()){ + } else if (instPli.getVersion() == curPli.getVersion()) { // 同版本覆盖 curPli.setPendingCover(instPli); curPli.setPendingDelete(null); @@ -324,6 +328,10 @@ private void updateOrLater(PluginInfo curPli, PluginInfo instPli) { } } + private static int getPluginType(PluginInfo pluginInfo) { + return pluginInfo == null ? PLUGIN_TYPE_INVALID : pluginInfo.isPnPlugin() ? PLUGIN_TYPE_PN : PLUGIN_TYPE_APK; + } + private void updatePendingUpdate(PluginInfo curPli, PluginInfo instPli, PluginInfo curUpdatePli) { if (curUpdatePli.getVersion() < instPli.getVersion()) { // 现在的版本比之前"打算要更新"的版本还要新(形象的称之为“夹心层”),则删除掉该“夹心层”的版本,然后换成这个更新的 @@ -589,7 +597,7 @@ private void addToRunningPluginsLocked(String processName, int pid, String plugi l.add(pluginName); if (LogDebug.LOG) { - LogDebug.d(TAG, "addToRunningPluginsLocked: Added! pl =" + l +"; map=" + mProcess2PluginsMap); + LogDebug.d(TAG, "addToRunningPluginsLocked: Added! pl =" + l + "; map=" + mProcess2PluginsMap); } } 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 diff --git a/replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/debugger/PluginDebugger.groovy b/replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/debugger/PluginDebugger.groovy index 8b86b411..448317a5 100755 --- a/replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/debugger/PluginDebugger.groovy +++ b/replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/debugger/PluginDebugger.groovy @@ -43,7 +43,7 @@ class PluginDebugger { String archivesBaseName = globalScope.getArchivesBaseName(); String apkBaseName = archivesBaseName + "-" + variantConfiguration.getBaseName() - File apkDir = new File(globalScope.getBuildDir(), "outputs/apk") + File apkDir = new File(globalScope.getBuildDir(), "outputs" + File.separator + "apk") String unsigned = (variantConfiguration.getSigningConfig() == null ? "-unsigned.apk" @@ -52,6 +52,10 @@ class PluginDebugger { apkFile = new File(apkDir, apkName) + if (!apkFile.exists() || apkFile.length() == 0) { + apkFile = new File(apkDir, variantConfiguration.getBaseName() + File.separator + apkName) + } + adbFile = globalScope.androidBuilder.sdkInfo.adb; } diff --git a/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePlugin.java b/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePlugin.java index 882c7133..5521f49d 100644 --- a/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePlugin.java +++ b/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePlugin.java @@ -36,6 +36,8 @@ import com.qihoo360.replugin.packages.PluginRunningList; import com.qihoo360.replugin.utils.ParcelUtils; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -363,18 +365,18 @@ public static boolean isForDev() { } /** - * 获取SDK的版本信息 + * 获取当前版本 * - * @return SDK的版本,如2.0.0等 - * @since 2.0.0 + * @return 版本号,如2.2.2等 + * @since 2.2.2 */ - public static String getSDKVersion() { + public static String getVersion() { if (!RePluginFramework.mHostInitialized) { return null; } try { - return (String) ProxyRePluginVar.getSDKVersion.call(null); + return (String) ProxyRePluginVar.getVersion.call(null); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); @@ -384,6 +386,17 @@ public static String getSDKVersion() { return null; } + /** + * 获取SDK的版本信息 + * + * @return SDK的版本,如2.0.0等 + * @since 2.0.0 + * @deprecated 已废弃,请使用 getVersion() 方法 + */ + public static String getSDKVersion() { + return getVersion(); + } + /** * 加载插件,并获取插件的包信息

* 注意:这里会尝试加载插件,并释放其Jar包。但不会读取资源,也不会释放oat/odex

@@ -980,7 +993,9 @@ public static boolean registerGlobalBinderDelayed(String name, IBinderGetter get try { Object obj = ProxyRePluginVar.registerGlobalBinderDelayed.call(null, name, getter); - + if (obj != null) { + return (Boolean) obj; + } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); @@ -1156,6 +1171,28 @@ public static boolean isHostInitialized() { return RePluginFramework.isHostInitialized(); } + /** + * dump RePlugin框架运行时的详细信息,包括:Activity 坑位映射表,正在运行的 Service,以及详细的插件信息 + * + * @param fd + * @param writer + * @param args + * @since 2.2.2 + */ + public static void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (!RePluginFramework.mHostInitialized) { + return; + } + + try { + ProxyRePluginVar.dump.call(null, fd, writer, args); + } catch (Exception e) { + if (LogDebug.LOG) { + e.printStackTrace(); + } + } + } + static class ProxyRePluginVar { private static MethodInvoker install; @@ -1178,7 +1215,7 @@ static class ProxyRePluginVar { private static MethodInvoker isForDev; - private static MethodInvoker getSDKVersion; + private static MethodInvoker getVersion; private static MethodInvoker fetchPackageInfo; @@ -1232,6 +1269,8 @@ static class ProxyRePluginVar { private static MethodInvoker unregisterHookingClass; + private static MethodInvoker dump; + static void initLocked(final ClassLoader classLoader) { // 初始化Replugin的相关方法 @@ -1255,7 +1294,7 @@ static void initLocked(final ClassLoader classLoader) { createIntent = new MethodInvoker(classLoader, rePlugin, "createIntent", new Class[]{String.class, String.class}); createComponentName = new MethodInvoker(classLoader, rePlugin, "createComponentName", new Class[]{String.class, String.class}); isForDev = new MethodInvoker(classLoader, rePlugin, "isForDev", new Class[]{}); - getSDKVersion = new MethodInvoker(classLoader, rePlugin, "getSDKVersion", new Class[]{}); + getVersion = new MethodInvoker(classLoader, rePlugin, "getVersion", new Class[]{}); fetchPackageInfo = new MethodInvoker(classLoader, rePlugin, "fetchPackageInfo", new Class[]{String.class}); fetchResources = new MethodInvoker(classLoader, rePlugin, "fetchResources", new Class[]{String.class}); fetchClassLoader = new MethodInvoker(classLoader, rePlugin, "fetchClassLoader", new Class[]{String.class}); @@ -1290,6 +1329,7 @@ static void initLocked(final ClassLoader classLoader) { registerHookingClass = new MethodInvoker(classLoader, rePlugin, "registerHookingClass", new Class[]{String.class, ComponentName.class, Class.class}); isHookingClass = new MethodInvoker(classLoader, rePlugin, "isHookingClass", new Class[]{ComponentName.class}); unregisterHookingClass = new MethodInvoker(classLoader, rePlugin, "unregisterHookingClass", new Class[]{String.class}); + dump = new MethodInvoker(classLoader, rePlugin, "dump", new Class[]{FileDescriptor.class, PrintWriter.class, (new String[0]).getClass()}); } } } diff --git a/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginPreferenceActivity.java b/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginPreferenceActivity.java new file mode 100644 index 00000000..d8250aee --- /dev/null +++ b/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginPreferenceActivity.java @@ -0,0 +1,104 @@ +/* + * 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.loader.a; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.PreferenceActivity; + +import com.qihoo360.replugin.RePluginInternal; +import com.qihoo360.replugin.helper.LogRelease; + +/** + * @author RePlugin Team + */ + +public class PluginPreferenceActivity extends PreferenceActivity { + + @Override + protected void attachBaseContext(Context newBase) { + newBase = RePluginInternal.createActivityContext(this, newBase); + super.attachBaseContext(newBase); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + // + RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); + + super.onCreate(savedInstanceState); + + // + RePluginInternal.handleActivityCreate(this, savedInstanceState); + } + + @Override + protected void onDestroy() { + // + RePluginInternal.handleActivityDestroy(this); + + super.onDestroy(); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + // + RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); + + try { + super.onRestoreInstanceState(savedInstanceState); + } catch (Throwable e) { + // Added by Jiongxuan Zhang + // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 + // java.lang.IllegalArgumentException: Wrong state class异常 + // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 + // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity + // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 + // 解决:将其Catch住,这样系统在找ViewState时不会出错。 + // 后遗症: + // 1、可能无法恢复系统级View的保存的状态; + // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 + if (LogRelease.LOGR) { + LogRelease.e("PluginActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); + } + } + } + + @Override + public void startActivity(Intent intent) { + // + if (RePluginInternal.startActivity(this, intent)) { + // 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivity() + return; + } + + super.startActivity(intent); + } + + @Override + public void startActivityForResult(Intent intent, int requestCode) { + // + if (RePluginInternal.startActivityForResult(this, intent, requestCode)) { + // 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivityForResult() + return; + } + + super.startActivityForResult(intent, requestCode); + + } +} diff --git a/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/p/PluginProviderClient.java b/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/p/PluginProviderClient.java index 2c983b6f..2453fc4b 100644 --- a/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/p/PluginProviderClient.java +++ b/replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/p/PluginProviderClient.java @@ -21,17 +21,16 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.CancellationSignal; -import com.qihoo360.replugin.RePluginFramework; import com.qihoo360.replugin.MethodInvoker; +import com.qihoo360.replugin.RePluginFramework; import com.qihoo360.replugin.helper.LogDebug; /** - * 一种能够对【插件】的服务进行:启动、停止、绑定、解绑等功能的类 - * 所有针对插件命令的操作,均从此类开始。 - *

- * 外界可直接使用此类 + * 一种能够对【插件】的Provider做增加、删除、改变、查询的接口。 + * 就像使用ContentResolver一样 * * @author RePlugin Team */ @@ -67,7 +66,7 @@ public static Cursor query(Context c, Uri uri, String[] projection, String selec * * @see android.content.ContentResolver#query(Uri, String[], String, String[], String, CancellationSignal) */ - @TargetApi(16) + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { if (c == null) { return null; @@ -215,7 +214,11 @@ public static void initLocked(final ClassLoader classLoader) { // String rePluginProviderClient = "com.qihoo360.loader2.mgr.PluginProviderClient"; query = new MethodInvoker(classLoader, rePluginProviderClient, "query", new Class[]{Context.class, Uri.class, String[].class, String.class, String[].class, String.class}); - query2 = new MethodInvoker(classLoader, rePluginProviderClient, "query", new Class[]{Context.class, Uri.class, String[].class, String.class, String[].class, String.class, CancellationSignal.class}); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + query2 = new MethodInvoker(classLoader, rePluginProviderClient, "query", new Class[]{Context.class, Uri.class, String[].class, String.class, String[].class, String.class, CancellationSignal.class}); + } + insert = new MethodInvoker(classLoader, rePluginProviderClient, "insert", new Class[]{Context.class, Uri.class, ContentValues.class}); bulkInsert = new MethodInvoker(classLoader, rePluginProviderClient, "bulkInsert", new Class[]{Context.class, Uri.class, ContentValues[].class}); delete = new MethodInvoker(classLoader, rePluginProviderClient, "delete", new Class[]{Context.class, Uri.class, String.class, String[].class}); diff --git a/replugin-sample-extra/README.md b/replugin-sample-extra/README.md new file mode 100644 index 00000000..e67e6cb6 --- /dev/null +++ b/replugin-sample-extra/README.md @@ -0,0 +1,5 @@ +# replugin-sample-extra 中是一些更高级的示例 + +其中,replugin-sample-extra/fresco 是对 Fresco 的使用,实现了宿主和插件公用一份Fresco代码。 + +FrescoHost是宿主代码,FrescoPlugin是插件代码。 diff --git a/replugin-sample-extra/fresco/FrescoHost/.gitignore b/replugin-sample-extra/fresco/FrescoHost/.gitignore new file mode 100644 index 00000000..39fb081a --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/replugin-sample-extra/fresco/FrescoHost/app/.gitignore b/replugin-sample-extra/fresco/FrescoHost/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/replugin-sample-extra/fresco/FrescoHost/app/build.gradle b/replugin-sample-extra/fresco/FrescoHost/app/build.gradle new file mode 100644 index 00000000..87387be3 --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/build.gradle @@ -0,0 +1,76 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.2" + + defaultConfig { + applicationId "com.qihoo360.replugin.fresco.host" + minSdkVersion 14 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + } + } +} + +/* + * 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. + */ + +// ATTENTION!!! Must be PLACED AFTER "android{}" to read the applicationId +apply plugin: 'replugin-host-gradle' + +/** + * 配置项均为可选配置,默认无需添加 + * 更多可选配置项参见replugin-host-gradle的RepluginConfig类 + * 可更改配置项参见 自动生成RePluginHostConfig.java + */ +repluginHostConfig { + + /** + * 是否使用 AppCompat 库 + * 不需要个性化配置时,无需添加 + */ + useAppCompat = true +} + +dependencies { + compile 'com.qihoo360.replugin:replugin-host-lib:2.2.1' + + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + + compile 'com.android.support:appcompat-v7:26.0.0-alpha1' + testCompile 'junit:junit:4.12' + + // fresco依赖bolts-android + compile "com.parse.bolts:bolts-android:1.4.0" +} diff --git a/replugin-sample-extra/fresco/FrescoHost/app/libs/armeabi/libimagepipeline.so b/replugin-sample-extra/fresco/FrescoHost/app/libs/armeabi/libimagepipeline.so new file mode 100644 index 00000000..cff4884f Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/libs/armeabi/libimagepipeline.so differ diff --git a/replugin-sample-extra/fresco/FrescoHost/app/libs/drawee-modified-1.7.1.jar b/replugin-sample-extra/fresco/FrescoHost/app/libs/drawee-modified-1.7.1.jar new file mode 100644 index 00000000..b514d339 Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/libs/drawee-modified-1.7.1.jar differ diff --git a/replugin-sample-extra/fresco/FrescoHost/app/libs/fbcore-1.7.1.jar b/replugin-sample-extra/fresco/FrescoHost/app/libs/fbcore-1.7.1.jar new file mode 100644 index 00000000..ce0053ff Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/libs/fbcore-1.7.1.jar differ diff --git a/replugin-sample-extra/fresco/FrescoHost/app/libs/fresco-1.7.1.jar b/replugin-sample-extra/fresco/FrescoHost/app/libs/fresco-1.7.1.jar new file mode 100644 index 00000000..7190ef51 Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/libs/fresco-1.7.1.jar differ diff --git a/replugin-sample-extra/fresco/FrescoHost/app/libs/imagepipeline-1.7.1.jar b/replugin-sample-extra/fresco/FrescoHost/app/libs/imagepipeline-1.7.1.jar new file mode 100644 index 00000000..51e2b542 Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/libs/imagepipeline-1.7.1.jar differ diff --git a/replugin-sample-extra/fresco/FrescoHost/app/libs/imagepipeline-base-1.7.1.jar b/replugin-sample-extra/fresco/FrescoHost/app/libs/imagepipeline-base-1.7.1.jar new file mode 100644 index 00000000..d48eff6e Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/libs/imagepipeline-base-1.7.1.jar differ diff --git a/replugin-sample-extra/fresco/FrescoHost/app/proguard-rules.pro b/replugin-sample-extra/fresco/FrescoHost/app/proguard-rules.pro new file mode 100644 index 00000000..d14c450f --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\Android\android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/AndroidManifest.xml b/replugin-sample-extra/fresco/FrescoHost/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d716e32a --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/assets/plugins/plugin1.jar b/replugin-sample-extra/fresco/FrescoHost/app/src/main/assets/plugins/plugin1.jar new file mode 100644 index 00000000..e41d11b0 Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/assets/plugins/plugin1.jar differ diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/facebook/fresco/patch/DraweeStyleableCallbackImpl.java b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/facebook/fresco/patch/DraweeStyleableCallbackImpl.java new file mode 100644 index 00000000..5cae30e5 --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/facebook/fresco/patch/DraweeStyleableCallbackImpl.java @@ -0,0 +1,293 @@ +/* + * 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.facebook.fresco.patch; + +import android.content.Context; + +import com.facebook.drawee.generic.DraweeStyleableCallback; + +import java.lang.reflect.Field; + +/** + * DraweeStyleableCallbackImpl + * + * 自定义属性回调接口 + * + * @author RePlugin Team + */ +public class DraweeStyleableCallbackImpl implements DraweeStyleableCallback { + + private Context mContext; + + DraweeStyleableCallbackImpl(Context context) { + this.mContext = context; + } + + /** + * 反射得到样式表数组,如:R.styleable.SimpleDraweeView + * + * @param context + * @param name + * @return + */ + private static int[] getStyleableArray(Context context, String name) { + try { + String className = context.getPackageName() + ".R$styleable"; + Field[] fields = Class.forName(className).getFields(); + for (Field field : fields) { + if (field.getName().equals(name)) { + return (int[]) field.get(null); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + /** + * 反射得到样式表数组下的具体资源,如:R.styleable.GenericDraweeHierarchy_placeholderImage + * + * @param context + * @param styleableName + * @param styleableFieldName + * @return + */ + private static int getStyleableFieldId(Context context, String styleableName, String styleableFieldName) { + String className = context.getPackageName() + ".R"; + String type = "styleable"; + String name = styleableName + "_" + styleableFieldName; + + try { + Class cla = Class.forName(className); + for (Class childClass : cla.getClasses()) { + String simpleName = childClass.getSimpleName(); + if (simpleName.equals(type)) { + for (Field field : childClass.getFields()) { + String fieldName = field.getName(); + if (fieldName.equals(name)) { + return (int) field.get(null); + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return 0; + } + + @Override + public int[] getSimpleDraweeView() { + // 替代 R.styleable.SimpleDraweeView; + return getStyleableArray(mContext, "SimpleDraweeView"); + } + + @Override + public int[] getGenericDraweeHierarchy() { + // 替代 R.styleable.GenericDraweeHierarchy; + return getStyleableArray(mContext, "GenericDraweeHierarchy"); + } + + @Override + public int getActualImageScaleType() { + // 替代 R.styleable.GenericDraweeHierarchy_actualImageScaleType; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "actualImageScaleType"); + } + + @Override + public int getPlaceholderImage() { + // 替代:R.styleable.GenericDraweeHierarchy_placeholderImage + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "placeholderImage"); + } + + @Override + public int getPressedStateOverlayImage() { + // 替代:R.styleable.GenericDraweeHierarchy_pressedStateOverlayImage + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "pressedStateOverlayImage"); + } + + @Override + public int getProgressBarImage() { + // R.styleable.GenericDraweeHierarchy_progressBarImage; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "progressBarImage"); + } + + @Override + public int getFadeDuration() { + // R.styleable.GenericDraweeHierarchy_fadeDuration; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "fadeDuration"); + } + + @Override + public int getViewAspectRatio() { + // R.styleable.GenericDraweeHierarchy_viewAspectRatio; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "viewAspectRatio"); + } + + @Override + public int getPlaceholderImageScaleType() { + // R.styleable.GenericDraweeHierarchy_placeholderImageScaleType; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "placeholderImageScaleType"); + } + + @Override + public int getRetryImage() { + // R.styleable.GenericDraweeHierarchy_retryImage; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "retryImage"); + } + + @Override + public int getRetryImageScaleType() { + // R.styleable.GenericDraweeHierarchy_retryImageScaleType; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "retryImageScaleType"); + } + + @Override + public int getFailureImage() { + // return R.styleable.GenericDraweeHierarchy_failureImage; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "failureImage"); + } + + @Override + public int getFailureImageScaleType() { + // R.styleable.GenericDraweeHierarchy_failureImageScaleType; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "failureImageScaleType"); + } + + @Override + public int getProgressBarImageScaleType() { + // R.styleable.GenericDraweeHierarchy_progressBarImageScaleType; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "progressBarImageScaleType"); + } + + @Override + public int getProgressBarAutoRotateInterval() { + // R.styleable.GenericDraweeHierarchy_progressBarAutoRotateInterval; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "progressBarAutoRotateInterval"); + } + + @Override + public int getBackgroundImage() { + // R.styleable.GenericDraweeHierarchy_backgroundImage; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "backgroundImage"); + } + + @Override + public int getOverlayImage() { + // R.styleable.GenericDraweeHierarchy_overlayImage; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "overlayImage"); + } + + @Override + public int getRoundAsCircle() { + // R.styleable.GenericDraweeHierarchy_roundAsCircle; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundAsCircle"); + } + + @Override + public int getRoundedCornerRadius() { + // R.styleable.GenericDraweeHierarchy_roundedCornerRadius; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundedCornerRadius"); + } + + @Override + public int getRoundTopLeft() { + // R.styleable.GenericDraweeHierarchy_roundTopLeft; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundTopLeft"); + } + + @Override + public int getRoundTopRight() { + // R.styleable.GenericDraweeHierarchy_roundTopRight; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundTopRight"); + } + + @Override + public int getRoundBottomLeft() { + // R.styleable.GenericDraweeHierarchy_roundBottomLeft; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundBottomLeft"); + } + + @Override + public int getRoundBottomRight() { + // R.styleable.GenericDraweeHierarchy_roundBottomRight; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundBottomRight"); + } + + @Override + public int getRoundTopStart() { + // R.styleable.GenericDraweeHierarchy_roundTopStart; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundTopStart"); + } + + @Override + public int getRoundTopEnd() { + // R.styleable.GenericDraweeHierarchy_roundTopEnd; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundTopEnd"); + } + + @Override + public int getRoundBottomStart() { + // R.styleable.GenericDraweeHierarchy_roundBottomStart; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundBottomStart"); + } + + @Override + public int getRoundBottomEnd() { + // R.styleable.GenericDraweeHierarchy_roundBottomEnd; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundBottomEnd"); + } + + @Override + public int getRoundWithOverlayColor() { + // R.styleable.GenericDraweeHierarchy_roundWithOverlayColor; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundWithOverlayColor"); + } + + @Override + public int getRoundingBorderWidth() { + // R.styleable.GenericDraweeHierarchy_roundingBorderWidth; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundingBorderWidth"); + } + + @Override + public int getRoundingBorderColor() { + // R.styleable.GenericDraweeHierarchy_roundingBorderColor; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundingBorderColor"); + } + + @Override + public int getRoundingBorderPadding() { + // R.styleable.GenericDraweeHierarchy_roundingBorderPadding; + return getStyleableFieldId(mContext, "GenericDraweeHierarchy", "roundingBorderPadding"); + } + + @Override + public int getActualImageUri() { + // R.styleable.SimpleDraweeView_actualImageUri; + return getStyleableFieldId(mContext, "SimpleDraweeView", "actualImageUri"); + } + + @Override + public int getActualImageResource() { + // R.styleable.SimpleDraweeView_actualImageResource; + return getStyleableFieldId(mContext, "SimpleDraweeView", "actualImageResource"); + } +} \ No newline at end of file diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/facebook/fresco/patch/FrescoPatch.java b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/facebook/fresco/patch/FrescoPatch.java new file mode 100644 index 00000000..c295a790 --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/facebook/fresco/patch/FrescoPatch.java @@ -0,0 +1,42 @@ +/* + * 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.facebook.fresco.patch; + +import android.content.Context; + +import com.facebook.drawee.generic.GenericDraweeHierarchyInflater; +import com.facebook.drawee.view.SimpleDraweeView; + +/** + * 为 “自己编译好的fresco drawee 模块(对应drawee-modified-1.7.1.jar)” 设置回调接口 + * + * @author RePlugin Team + */ +public class FrescoPatch { + + /** + * 初始化 + *

+ * 为SimpleDraweeView设置回调 + * 为GenericDraweeHierarchyInflater设置回调 + */ + public static void initialize(Context context) { + DraweeStyleableCallbackImpl draweeStyleableCallback = new DraweeStyleableCallbackImpl(context); + SimpleDraweeView.setDraweeStyleableCallback(draweeStyleableCallback); + GenericDraweeHierarchyInflater.setDraweeStyleableCallback(draweeStyleableCallback); + } +} \ No newline at end of file diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/HostApp.java b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/HostApp.java new file mode 100644 index 00000000..b18b24dd --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/HostApp.java @@ -0,0 +1,56 @@ +/* + * 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.fresco.host; + +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.fresco.patch.FrescoPatch; +import com.qihoo360.replugin.RePluginApplication; +import com.qihoo360.replugin.RePluginConfig; + +/** + * HostApp + * + * @author RePlugin Team + */ +public class HostApp extends RePluginApplication { + + @Override + public void onCreate() { + super.onCreate(); + + // 初始化Fresco + Fresco.initialize(this); + + // 初始化FrescoPath + FrescoPatch.initialize(this); + } + + /** + * RePlugin允许提供各种“自定义”的行为,让您“无需修改源代码”,即可实现相应的功能 + */ + @Override + protected RePluginConfig createConfig() { + RePluginConfig c = new RePluginConfig(); + + // 允许“插件使用宿主类” + // 打开这个开关之后,当插件ClassLoader找不到类时,会去看宿主是否有这个类 + // 从而,实现插件复用宿主中的Java类 + c.setUseHostClassIfNotFound(true); + + return c; + } +} \ No newline at end of file diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/HostFrescoActivity.java b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/HostFrescoActivity.java new file mode 100644 index 00000000..74cbaae5 --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/HostFrescoActivity.java @@ -0,0 +1,42 @@ +/* + * 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.fresco.host; + +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +import com.facebook.drawee.view.SimpleDraweeView; + +/** + * 宿主中使用fresco + * + * @author RePlugin Team + */ +public class HostFrescoActivity extends AppCompatActivity { + + private static final String IMAGE_URL = "https://img1.doubanio.com/view/photo/large/public/p2504463708.jpg"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_host_fresco); + + SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.image); + draweeView.setImageURI(Uri.parse(IMAGE_URL)); + } +} \ No newline at end of file diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/MainActivity.java b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/MainActivity.java new file mode 100644 index 00000000..177d0703 --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/java/com/qihoo360/replugin/fresco/host/MainActivity.java @@ -0,0 +1,84 @@ +/* + * 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.fresco.host; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.Button; + +import com.qihoo360.replugin.RePlugin; + +/** + * @author RePlugin Team + */ +public class MainActivity extends AppCompatActivity { + + private Button mButton1; + private Button mButton2; + + Handler mHandler = new Handler(Looper.getMainLooper()) { + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + RePlugin.startActivity(MainActivity.this, new Intent(), "plugin1", "com.qihoo360.replugin.fresco.plugin.MainActivity"); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mButton1 = (Button) findViewById(R.id.button1); + mButton2 = (Button) findViewById(R.id.button2); + + mButton1.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + startActivity(new Intent(MainActivity.this, HostFrescoActivity.class)); + } + }); + + mButton2.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + + new Thread() { + @Override + public void run() { + super.run(); + + long begin = System.currentTimeMillis(); + RePlugin.preload("plugin1"); + Log.d("FrescoHost", "preload use:" + (System.currentTimeMillis() - begin)); + + mHandler.sendEmptyMessage(-1); + } + }.start(); + } + }); + } +} \ No newline at end of file diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/layout/activity_host_fresco.xml b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/layout/activity_host_fresco.xml new file mode 100644 index 00000000..4bceeaef --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/layout/activity_host_fresco.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/layout/activity_main.xml b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..bdd222cb --- /dev/null +++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,27 @@ + + + + + +