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
@@ -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 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-hdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..cde69bcc
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9a078e3e
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-mdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..c133a0cb
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..efc028a6
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..bfa42f0e
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..3af2608a
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..324e72cd
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9bec2e62
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..aee44e13
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..34947cd6
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/attrs.xml b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..7c9c62bc
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/attrs.xml
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/colors.xml b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..3ab3e9cb
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/strings.xml b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..14ce275d
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ FrescoHost
+
diff --git a/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/styles.xml b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..5885930d
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/replugin-sample-extra/fresco/FrescoHost/build.gradle b/replugin-sample-extra/fresco/FrescoHost/build.gradle
new file mode 100644
index 00000000..8694d590
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.0'
+ classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.1'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/replugin-sample-extra/fresco/FrescoHost/gradle.properties b/replugin-sample-extra/fresco/FrescoHost/gradle.properties
new file mode 100644
index 00000000..aac7c9b4
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+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
+# org.gradle.parallel=true
diff --git a/replugin-sample-extra/fresco/FrescoHost/gradle/wrapper/gradle-wrapper.jar b/replugin-sample-extra/fresco/FrescoHost/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..13372aef
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoHost/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/replugin-sample-extra/fresco/FrescoHost/gradle/wrapper/gradle-wrapper.properties b/replugin-sample-extra/fresco/FrescoHost/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..5ff3c91a
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jan 05 17:10:53 CST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/replugin-sample-extra/fresco/FrescoHost/gradlew b/replugin-sample-extra/fresco/FrescoHost/gradlew
new file mode 100644
index 00000000..9d82f789
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/replugin-sample-extra/fresco/FrescoHost/gradlew.bat b/replugin-sample-extra/fresco/FrescoHost/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/replugin-sample-extra/fresco/FrescoHost/settings.gradle b/replugin-sample-extra/fresco/FrescoHost/settings.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoHost/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/.gitignore b/replugin-sample-extra/fresco/FrescoPlugin/.gitignore
new file mode 100644
index 00000000..39fb081a
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/.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/FrescoPlugin/app/.gitignore b/replugin-sample-extra/fresco/FrescoPlugin/app/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/build.gradle b/replugin-sample-extra/fresco/FrescoPlugin/app/build.gradle
new file mode 100644
index 00000000..a1d7b976
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/build.gradle
@@ -0,0 +1,55 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 26
+ buildToolsVersion "26.0.2"
+
+ defaultConfig {
+ applicationId "com.qihoo360.replugin.fresco.plugin"
+ minSdkVersion 14
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ sourceSets {
+ main {
+ jniLibs.srcDirs = ['libs']
+ }
+ }
+}
+
+apply plugin: 'replugin-plugin-gradle'
+
+dependencies {
+ compile 'com.qihoo360.replugin:replugin-plugin-lib:2.2.1'
+
+ compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
+
+ /** provided or compile */
+ if (rootProject.ext.isPlugin) {
+ // 作为插件,provided 方式引入
+ provided "com.parse.bolts:bolts-android:1.4.0"
+ provided files('libs/drawee-modified-1.7.1.jar')
+ provided files('libs/fbcore-1.7.1.jar')
+ provided files('libs/fresco-1.7.1.jar')
+ provided files('libs/imagepipeline-1.7.1.jar')
+ provided files('libs/imagepipeline-base-1.7.1.jar')
+ } else {
+ // 作为单品,compile 进 APK
+ compile "com.parse.bolts:bolts-android:1.4.0"
+
+ compile files('libs/fresco-1.7.1.jar')
+ compile files('libs/fbcore-1.7.1.jar')
+ compile files('libs/drawee-modified-1.7.1.jar')
+ compile files('libs/imagepipeline-1.7.1.jar')
+ compile files('libs/imagepipeline-base-1.7.1.jar')
+ }
+}
\ No newline at end of file
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/libs/armeabi/libimagepipeline.so b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/armeabi/libimagepipeline.so
new file mode 100644
index 00000000..cff4884f
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/armeabi/libimagepipeline.so differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/libs/drawee-modified-1.7.1.jar b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/drawee-modified-1.7.1.jar
new file mode 100644
index 00000000..07b44c95
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/drawee-modified-1.7.1.jar differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/libs/fbcore-1.7.1.jar b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/fbcore-1.7.1.jar
new file mode 100644
index 00000000..018cd774
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/fbcore-1.7.1.jar differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/libs/fresco-1.7.1.jar b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/fresco-1.7.1.jar
new file mode 100644
index 00000000..7a7fb90b
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/fresco-1.7.1.jar differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/libs/imagepipeline-1.7.1.jar b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/imagepipeline-1.7.1.jar
new file mode 100644
index 00000000..0bd9cbb8
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/imagepipeline-1.7.1.jar differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/libs/imagepipeline-base-1.7.1.jar b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/imagepipeline-base-1.7.1.jar
new file mode 100644
index 00000000..918727dc
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/libs/imagepipeline-base-1.7.1.jar differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/proguard-rules.pro b/replugin-sample-extra/fresco/FrescoPlugin/app/proguard-rules.pro
new file mode 100644
index 00000000..d14c450f
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/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/FrescoPlugin/app/src/main/AndroidManifest.xml b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..18e2a1eb
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/java/com/facebook/fresco/patch/DraweeStyleableCallbackImpl.java b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/java/com/facebook/fresco/patch/DraweeStyleableCallbackImpl.java
new file mode 100644
index 00000000..5cae30e5
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/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/FrescoPlugin/app/src/main/java/com/facebook/fresco/patch/FrescoPatch.java b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/java/com/facebook/fresco/patch/FrescoPatch.java
new file mode 100644
index 00000000..c295a790
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/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/FrescoPlugin/app/src/main/java/com/qihoo360/replugin/fresco/plugin/MainActivity.java b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/java/com/qihoo360/replugin/fresco/plugin/MainActivity.java
new file mode 100644
index 00000000..5b65500f
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/java/com/qihoo360/replugin/fresco/plugin/MainActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.plugin;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+import com.facebook.drawee.view.SimpleDraweeView;
+
+/**
+ * @author RePlugin Team
+ */
+public class MainActivity extends AppCompatActivity {
+
+ private static final String IMAGE_URL = "https://img1.doubanio.com/lpic/s3743777.jpg";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ SimpleDraweeView simpleDraweeView = (SimpleDraweeView) findViewById(R.id.image);
+ Uri uri = Uri.parse(IMAGE_URL);
+ simpleDraweeView.setImageURI(uri);
+ }
+}
\ No newline at end of file
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/java/com/qihoo360/replugin/fresco/plugin/PluginApp.java b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/java/com/qihoo360/replugin/fresco/plugin/PluginApp.java
new file mode 100644
index 00000000..ab503a56
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/java/com/qihoo360/replugin/fresco/plugin/PluginApp.java
@@ -0,0 +1,36 @@
+/*
+ * 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.plugin;
+
+import android.app.Application;
+
+import com.facebook.drawee.backends.pipeline.Fresco;
+import com.facebook.fresco.patch.FrescoPatch;
+
+/**
+ * @author RePlugin Team
+ */
+public class PluginApp extends Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Fresco.initialize(this);
+ FrescoPatch.initialize(this);
+ }
+}
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/layout/activity_main.xml b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..7daf4e5d
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-hdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..cde69bcc
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9a078e3e
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-mdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..c133a0cb
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..efc028a6
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..bfa42f0e
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..3af2608a
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..324e72cd
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9bec2e62
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..aee44e13
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..34947cd6
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/attrs.xml b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..7c9c62bc
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/attrs.xml
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/colors.xml b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..ccbc6416
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3C3C3B
+ #000000
+ #FF4081
+
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/strings.xml b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..7d544b67
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ FrescoPlugin
+
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/styles.xml b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..5885930d
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/build.gradle b/replugin-sample-extra/fresco/FrescoPlugin/build.gradle
new file mode 100644
index 00000000..652428c0
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/build.gradle
@@ -0,0 +1,32 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.0'
+ classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.1'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+/**
+ * 作为插件运行,设置为true;作为单独APK运行,设置为false
+ *
+ */
+project.ext {
+ isPlugin = true
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/gradle.properties b/replugin-sample-extra/fresco/FrescoPlugin/gradle.properties
new file mode 100644
index 00000000..aac7c9b4
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+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
+# org.gradle.parallel=true
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/gradle/wrapper/gradle-wrapper.jar b/replugin-sample-extra/fresco/FrescoPlugin/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..13372aef
Binary files /dev/null and b/replugin-sample-extra/fresco/FrescoPlugin/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/gradle/wrapper/gradle-wrapper.properties b/replugin-sample-extra/fresco/FrescoPlugin/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..94b50d3e
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jan 05 16:45:51 CST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/gradlew b/replugin-sample-extra/fresco/FrescoPlugin/gradlew
new file mode 100644
index 00000000..9d82f789
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/gradlew.bat b/replugin-sample-extra/fresco/FrescoPlugin/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/replugin-sample-extra/fresco/FrescoPlugin/settings.gradle b/replugin-sample-extra/fresco/FrescoPlugin/settings.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/replugin-sample-extra/fresco/FrescoPlugin/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/replugin-sample/host/app/src/main/AndroidManifest.xml b/replugin-sample/host/app/src/main/AndroidManifest.xml
index be049a1e..0abc4abf 100644
--- a/replugin-sample/host/app/src/main/AndroidManifest.xml
+++ b/replugin-sample/host/app/src/main/AndroidManifest.xml
@@ -43,6 +43,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample/host/app/src/main/assets/plugins/demo1.jar b/replugin-sample/host/app/src/main/assets/plugins/demo1.jar
index cd3c2c4b..2b087a8c 100644
Binary files a/replugin-sample/host/app/src/main/assets/plugins/demo1.jar and b/replugin-sample/host/app/src/main/assets/plugins/demo1.jar differ
diff --git a/replugin-sample/host/app/src/main/assets/plugins/webview.jar b/replugin-sample/host/app/src/main/assets/plugins/webview.jar
new file mode 100644
index 00000000..b9578a9a
Binary files /dev/null and b/replugin-sample/host/app/src/main/assets/plugins/webview.jar differ
diff --git a/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/FileProvider.java b/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/FileProvider.java
new file mode 100644
index 00000000..a14725fd
--- /dev/null
+++ b/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/FileProvider.java
@@ -0,0 +1,773 @@
+package com.qihoo360.replugin.sample.host;
+
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.OpenableColumns;
+import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+/**
+ * FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing
+ * of files associated with an app by creating a content:// {@link Uri} for a file
+ * instead of a file:/// {@link Uri}.
+ *
+ * A content URI allows you to grant read and write access using
+ * temporary access permissions. When you create an {@link Intent} containing
+ * a content URI, in order to send the content URI
+ * to a client app, you can also call {@link Intent#setFlags(int) Intent.setFlags()} to add
+ * permissions. These permissions are available to the client app for as long as the stack for
+ * a receiving {@link android.app.Activity} is active. For an {@link Intent} going to a
+ * {@link android.app.Service}, the permissions are available as long as the
+ * {@link android.app.Service} is running.
+ *
+ * In comparison, to control access to a file:/// {@link Uri} you have to modify the
+ * file system permissions of the underlying file. The permissions you provide become available to
+ * any app, and remain in effect until you change them. This level of access is
+ * fundamentally insecure.
+ *
+ * The increased level of file access security offered by a content URI
+ * makes FileProvider a key part of Android's security infrastructure.
+ *
+ * This overview of FileProvider includes the following topics:
+ *
+ * Since the default functionality of FileProvider includes content URI generation for files, you
+ * don't need to define a subclass in code. Instead, you can include a FileProvider in your app
+ * by specifying it entirely in XML. To specify the FileProvider component itself, add a
+ * <provider>
+ * element to your app manifest. Set the android:name attribute to
+ * android.support.v4.content.FileProvider. Set the android:authorities
+ * attribute to a URI authority based on a domain you control; for example, if you control the
+ * domain mydomain.com you should use the authority
+ * com.mydomain.fileprovider. Set the android:exported attribute to
+ * false; the FileProvider does not need to be public. Set the
+ * android:grantUriPermissions attribute to true, to allow you
+ * to grant temporary access to files. For example:
+ *
+ * If you want to override any of the default behavior of FileProvider methods, extend
+ * the FileProvider class and use the fully-qualified class name in the android:name
+ * attribute of the <provider> element.
+ *
Specifying Available Files
+ * A FileProvider can only generate a content URI for files in directories that you specify
+ * beforehand. To specify a directory, specify the its storage area and path in XML, using child
+ * elements of the <paths> element.
+ * For example, the following paths element tells FileProvider that you intend to
+ * request content URIs for the images/ subdirectory of your private file area.
+ *
+ * The <paths> element must contain one or more of the following child elements:
+ *
+ *
+ *
+ *
+ * <files-path name="name" path="path" />
+ *
+ *
+ *
+ * Represents files in the files/ subdirectory of your app's internal storage
+ * area. This subdirectory is the same as the value returned by {@link Context#getFilesDir()
+ * Context.getFilesDir()}.
+ *
+ *
+ * <external-path name="name" path="path" />
+ *
+ *
+ *
+ * Represents files in the root of your app's external storage area. The path
+ * {@link Context#getExternalFilesDir(String) Context.getExternalFilesDir()} returns the
+ * files/ subdirectory of this this root.
+ *
+ *
+ *
+ * <cache-path name="name" path="path" />
+ *
+ *
+ *
+ * Represents files in the cache subdirectory of your app's internal storage area. The root path
+ * of this subdirectory is the same as the value returned by {@link Context#getCacheDir()
+ * getCacheDir()}.
+ *
+ *
+ *
+ * These child elements all use the same attributes:
+ *
+ *
+ *
+ * name="name"
+ *
+ *
+ * A URI path segment. To enforce security, this value hides the name of the subdirectory
+ * you're sharing. The subdirectory name for this value is contained in the
+ * path attribute.
+ *
+ *
+ * path="path"
+ *
+ *
+ * The subdirectory you're sharing. While the name attribute is a URI path
+ * segment, the path value is an actual subdirectory name. Notice that the
+ * value refers to a subdirectory, not an individual file or files. You can't
+ * share a single file by its file name, nor can you specify a subset of files using
+ * wildcards.
+ *
+ *
+ *
+ * You must specify a child element of <paths> for each directory that contains
+ * files for which you want content URIs. For example, these XML elements specify two directories:
+ *
+ * Put the <paths> element and its children in an XML file in your project.
+ * For example, you can add them to a new file called res/xml/file_paths.xml.
+ * To link this file to the FileProvider, add a
+ * <meta-data> element
+ * as a child of the <provider> element that defines the FileProvider. Set the
+ * <meta-data> element's "android:name" attribute to
+ * android.support.FILE_PROVIDER_PATHS. Set the element's "android:resource" attribute
+ * to @xml/file_paths (notice that you don't specify the .xml
+ * extension). For example:
+ *
+ * To share a file with another app using a content URI, your app has to generate the content URI.
+ * To generate the content URI, create a new {@link File} for the file, then pass the {@link File}
+ * to {@link #getUriForFile(Context, String, File) getUriForFile()}. You can send the content URI
+ * returned by {@link #getUriForFile(Context, String, File) getUriForFile()} to another app in an
+ * {@link Intent}. The client app that receives the content URI can open the file
+ * and access its contents by calling
+ * {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
+ * ContentResolver.openFileDescriptor} to get a {@link ParcelFileDescriptor}.
+ *
+ * For example, suppose your app is offering files to other apps with a FileProvider that has the
+ * authority com.mydomain.fileprovider. To get a content URI for the file
+ * default_image.jpg in the images/ subdirectory of your internal storage
+ * add the following code:
+ *
+ * File imagePath = new File(Context.getFilesDir(), "images");
+ * File newFile = new File(imagePath, "default_image.jpg");
+ * Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
+ *
+ * As a result of the previous snippet,
+ * {@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI
+ * content://com.mydomain.fileprovider/my_images/default_image.jpg.
+ *
Granting Temporary Permissions to a URI
+ * To grant an access permission to a content URI returned from
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}, do one of the following:
+ *
+ *
+ * Call the method
+ * {@link Context#grantUriPermission(String, Uri, int)
+ * Context.grantUriPermission(package, Uri, mode_flags)} for the content://
+ * {@link Uri}, using the desired mode flags. This grants temporary access permission for the
+ * content URI to the specified package, according to the value of the
+ * the mode_flags parameter, which you can set to
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}
+ * or both. The permission remains in effect until you revoke it by calling
+ * {@link Context#revokeUriPermission(Uri, int) revokeUriPermission()} or until the device
+ * reboots.
+ *
+ *
+ * Put the content URI in an {@link Intent} by calling {@link Intent#setData(Uri) setData()}.
+ *
+ *
+ * Next, call the method {@link Intent#setFlags(int) Intent.setFlags()} with either
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} or
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} or both.
+ *
+ *
+ * Finally, send the {@link Intent} to
+ * another app. Most often, you do this by calling
+ * {@link android.app.Activity#setResult(int, Intent) setResult()}.
+ *
+ * Permissions granted in an {@link Intent} remain in effect while the stack of the receiving
+ * {@link android.app.Activity} is active. When the stack finishes, the permissions are
+ * automatically removed. Permissions granted to one {@link android.app.Activity} in a client
+ * app are automatically extended to other components of that app.
+ *
+ *
+ *
+ *
Serving a Content URI to Another App
+ *
+ * There are a variety of ways to serve the content URI for a file to a client app. One common way
+ * is for the client app to start your app by calling
+ * {@link android.app.Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()},
+ * which sends an {@link Intent} to your app to start an {@link android.app.Activity} in your app.
+ * In response, your app can immediately return a content URI to the client app or present a user
+ * interface that allows the user to pick a file. In the latter case, once the user picks the file
+ * your app can return its content URI. In both cases, your app returns the content URI in an
+ * {@link Intent} sent via {@link android.app.Activity#setResult(int, Intent) setResult()}.
+ *
+ *
+ * You can also put the content URI in a {@link android.content.ClipData} object and then add the
+ * object to an {@link Intent} you send to a client app. To do this, call
+ * {@link Intent#setClipData(ClipData) Intent.setClipData()}. When you use this approach, you can
+ * add multiple {@link android.content.ClipData} objects to the {@link Intent}, each with its own
+ * content URI. When you call {@link Intent#setFlags(int) Intent.setFlags()} on the {@link Intent}
+ * to set temporary access permissions, the same permissions are applied to all of the content
+ * URIs.
+ *
+ *
+ * Note: The {@link Intent#setClipData(ClipData) Intent.setClipData()} method is
+ * only available in platform version 16 (Android 4.1) and later. If you want to maintain
+ * compatibility with previous versions, you should send one content URI at a time in the
+ * {@link Intent}. Set the action to {@link Intent#ACTION_SEND} and put the URI in data by calling
+ * {@link Intent#setData setData()}.
+ *
+ */
+public class FileProvider extends ContentProvider {
+ private static final String[] COLUMNS = {
+ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
+
+ private static final String
+ META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS";
+
+ private static final String TAG_ROOT_PATH = "root-path";
+ private static final String TAG_FILES_PATH = "files-path";
+ private static final String TAG_CACHE_PATH = "cache-path";
+ private static final String TAG_EXTERNAL = "external-path";
+
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_PATH = "path";
+
+ private static final File DEVICE_ROOT = new File("/");
+
+ // @GuardedBy("sCache")
+ private static HashMap sCache = new HashMap();
+
+ private PathStrategy mStrategy;
+
+ /**
+ * The default FileProvider implementation does not need to be initialized. If you want to
+ * override this method, you must provide your own subclass of FileProvider.
+ */
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ /**
+ * After the FileProvider is instantiated, this method is called to provide the system with
+ * information about the provider.
+ *
+ * @param context A {@link Context} for the current component.
+ * @param info A {@link ProviderInfo} for the new provider.
+ */
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ super.attachInfo(context, info);
+
+ // Sanity check our security
+ if (info.exported) {
+ throw new SecurityException("Provider must not be exported");
+ }
+ if (!info.grantUriPermissions) {
+ throw new SecurityException("Provider must grant uri permissions");
+ }
+
+ mStrategy = getPathStrategy(context, info.authority);
+ }
+
+ /**
+ * Return a content URI for a given {@link File}. Specific temporary
+ * permissions for the content URI can be set with
+ * {@link Context#grantUriPermission(String, Uri, int)}, or added
+ * to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then
+ * {@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a
+ * content {@link Uri} for file paths defined in their <paths>
+ * meta-data element. See the Class Overview for more information.
+ *
+ * @param context A {@link Context} for the current component.
+ * @param authority The authority of a {@link FileProvider} defined in a
+ * {@code <provider>} element in your app's manifest.
+ * @param file A {@link File} pointing to the filename for which you want a
+ * content {@link Uri}.
+ * @return A content URI for the file.
+ * @throws IllegalArgumentException When the given {@link File} is outside
+ * the paths supported by the provider.
+ */
+ public static Uri getUriForFile(Context context, String authority, File file) {
+ final PathStrategy strategy = getPathStrategy(context, authority);
+ return strategy.getUriForFile(file);
+ }
+
+ /**
+ * Use a content URI returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file
+ * managed by the FileProvider.
+ * FileProvider reports the column names defined in {@link OpenableColumns}:
+ *
+ *
{@link OpenableColumns#DISPLAY_NAME}
+ *
{@link OpenableColumns#SIZE}
+ *
+ * For more information, see
+ * {@link ContentProvider#query(Uri, String[], String, String[], String)
+ * ContentProvider.query()}.
+ *
+ * @param uri A content URI returned by {@link #getUriForFile}.
+ * @param projection The list of columns to put into the {@link Cursor}. If null all columns are
+ * included.
+ * @param selection Selection criteria to apply. If null then all data that matches the content
+ * URI is returned.
+ * @param selectionArgs An array of {@link String}, containing arguments to bind to
+ * the selection parameter. The query method scans selection from left to
+ * right and iterates through selectionArgs, replacing the current "?" character in
+ * selection with the value at the current position in selectionArgs. The
+ * values are bound to selection as {@link String} values.
+ * @param sortOrder A {@link String} containing the column name(s) on which to sort
+ * the resulting {@link Cursor}.
+ * @return A {@link Cursor} containing the results of the query.
+ */
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ // ContentProvider has already checked granted permissions
+ final File file = mStrategy.getFileForUri(uri);
+
+ if (projection == null) {
+ projection = COLUMNS;
+ }
+
+ String[] cols = new String[projection.length];
+ Object[] values = new Object[projection.length];
+ int i = 0;
+ for (String col : projection) {
+ if (OpenableColumns.DISPLAY_NAME.equals(col)) {
+ cols[i] = OpenableColumns.DISPLAY_NAME;
+ values[i++] = file.getName();
+ } else if (OpenableColumns.SIZE.equals(col)) {
+ cols[i] = OpenableColumns.SIZE;
+ values[i++] = file.length();
+ }
+ }
+
+ cols = copyOf(cols, i);
+ values = copyOf(values, i);
+
+ final MatrixCursor cursor = new MatrixCursor(cols, 1);
+ cursor.addRow(values);
+ return cursor;
+ }
+
+ /**
+ * Returns the MIME type of a content URI returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}.
+ *
+ * @param uri A content URI returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}.
+ * @return If the associated file has an extension, the MIME type associated with that
+ * extension; otherwise application/octet-stream.
+ */
+ @Override
+ public String getType(Uri uri) {
+ // ContentProvider has already checked granted permissions
+ final File file = mStrategy.getFileForUri(uri);
+
+ final int lastDot = file.getName().lastIndexOf('.');
+ if (lastDot >= 0) {
+ final String extension = file.getName().substring(lastDot + 1);
+ final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mime != null) {
+ return mime;
+ }
+ }
+
+ return "application/octet-stream";
+ }
+
+ /**
+ * By default, this method throws an {@link UnsupportedOperationException}. You must
+ * subclass FileProvider if you want to provide different functionality.
+ */
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("No external inserts");
+ }
+
+ /**
+ * By default, this method throws an {@link UnsupportedOperationException}. You must
+ * subclass FileProvider if you want to provide different functionality.
+ */
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("No external updates");
+ }
+
+ /**
+ * Deletes the file associated with the specified content URI, as
+ * returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this
+ * method does not throw an {@link IOException}; you must check its return value.
+ *
+ * @param uri A content URI for a file, as returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}.
+ * @param selection Ignored. Set to {@code null}.
+ * @param selectionArgs Ignored. Set to {@code null}.
+ * @return 1 if the delete succeeds; otherwise, 0.
+ */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ // ContentProvider has already checked granted permissions
+ final File file = mStrategy.getFileForUri(uri);
+ return file.delete() ? 1 : 0;
+ }
+
+ /**
+ * By default, FileProvider automatically returns the
+ * {@link ParcelFileDescriptor} for a file associated with a content://
+ * {@link Uri}. To get the {@link ParcelFileDescriptor}, call
+ * {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
+ * ContentResolver.openFileDescriptor}.
+ *
+ * To override this method, you must provide your own subclass of FileProvider.
+ *
+ * @param uri A content URI associated with a file, as returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}.
+ * @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and
+ * write access, or "rwt" for read and write access that truncates any existing file.
+ * @return A new {@link ParcelFileDescriptor} with which you can access the file.
+ */
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ // ContentProvider has already checked granted permissions
+ final File file = mStrategy.getFileForUri(uri);
+ final int fileMode = modeToMode(mode);
+ return ParcelFileDescriptor.open(file, fileMode);
+ }
+
+ /**
+ * Return {@link FileProvider.PathStrategy} for given authority, either by parsing or
+ * returning from cache.
+ */
+ private static PathStrategy getPathStrategy(Context context, String authority) {
+ PathStrategy strat;
+ synchronized (sCache) {
+ strat = sCache.get(authority);
+ if (strat == null) {
+ try {
+ strat = parsePathStrategy(context, authority);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
+ } catch (XmlPullParserException e) {
+ throw new IllegalArgumentException(
+ "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
+ }
+ sCache.put(authority, strat);
+ }
+ }
+ return strat;
+ }
+
+ /**
+ * Parse and return {@link PathStrategy} for given authority as defined in
+ * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
+ *
+ * @see #getPathStrategy(Context, String)
+ */
+ private static PathStrategy parsePathStrategy(Context context, String authority)
+ throws IOException, XmlPullParserException {
+ final SimplePathStrategy strat = new SimplePathStrategy(authority);
+
+ final ProviderInfo info = context.getPackageManager()
+ .resolveContentProvider(authority, PackageManager.GET_META_DATA);
+ final XmlResourceParser in = info.loadXmlMetaData(
+ context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
+ if (in == null) {
+ throw new IllegalArgumentException(
+ "Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data");
+ }
+
+ int type;
+ while ((type = in.next()) != END_DOCUMENT) {
+ if (type == START_TAG) {
+ final String tag = in.getName();
+
+ final String name = in.getAttributeValue(null, ATTR_NAME);
+ String path = in.getAttributeValue(null, ATTR_PATH);
+
+ File target = null;
+ if (TAG_ROOT_PATH.equals(tag)) {
+ target = buildPath(DEVICE_ROOT, path);
+ } else if (TAG_FILES_PATH.equals(tag)) {
+ target = buildPath(context.getFilesDir(), path);
+ } else if (TAG_CACHE_PATH.equals(tag)) {
+ target = buildPath(context.getCacheDir(), path);
+ } else if (TAG_EXTERNAL.equals(tag)) {
+ target = buildPath(Environment.getExternalStorageDirectory(), path);
+ }
+
+ if (target != null) {
+ strat.addRoot(name, target);
+ }
+ }
+ }
+
+ return strat;
+ }
+
+ /**
+ * Strategy for mapping between {@link File} and {@link Uri}.
+ *
+ * Strategies must be symmetric so that mapping a {@link File} to a
+ * {@link Uri} and then back to a {@link File} points at the original
+ * target.
+ *
+ * Strategies must remain consistent across app launches, and not rely on
+ * dynamic state. This ensures that any generated {@link Uri} can still be
+ * resolved if your process is killed and later restarted.
+ *
+ * @see FileProvider.SimplePathStrategy
+ */
+ interface PathStrategy {
+ /**
+ * Return a {@link Uri} that represents the given {@link File}.
+ */
+ public Uri getUriForFile(File file);
+
+ /**
+ * Return a {@link File} that represents the given {@link Uri}.
+ */
+ public File getFileForUri(Uri uri);
+ }
+
+ /**
+ * Strategy that provides access to files living under a narrow whitelist of
+ * filesystem roots. It will throw {@link SecurityException} if callers try
+ * accessing files outside the configured roots.
+ *
+ * For example, if configured with
+ * {@code addRoot("myfiles", context.getFilesDir())}, then
+ * {@code context.getFileStreamPath("foo.txt")} would map to
+ * {@code content://myauthority/myfiles/foo.txt}.
+ */
+ static class SimplePathStrategy implements PathStrategy {
+ private final String mAuthority;
+ private final HashMap mRoots = new HashMap();
+
+ public SimplePathStrategy(String authority) {
+ mAuthority = authority;
+ }
+
+ /**
+ * Add a mapping from a name to a filesystem root. The provider only offers
+ * access to files that live under configured roots.
+ */
+ public void addRoot(String name, File root) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("Name must not be empty");
+ }
+
+ try {
+ // Resolve to canonical path to keep path checking fast
+ root = root.getCanonicalFile();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to resolve canonical path for " + root, e);
+ }
+
+ mRoots.put(name, root);
+ }
+
+ @Override
+ public Uri getUriForFile(File file) {
+ String path;
+ try {
+ path = file.getCanonicalPath();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
+ }
+
+ // Find the most-specific root path
+ Map.Entry mostSpecific = null;
+ for (Map.Entry root : mRoots.entrySet()) {
+ final String rootPath = root.getValue().getPath();
+ if (path.startsWith(rootPath) && (mostSpecific == null
+ || rootPath.length() > mostSpecific.getValue().getPath().length())) {
+ mostSpecific = root;
+ }
+ }
+
+ if (mostSpecific == null) {
+ throw new IllegalArgumentException(
+ "Failed to find configured root that contains " + path);
+ }
+
+ // Start at first char of path under root
+ final String rootPath = mostSpecific.getValue().getPath();
+ if (rootPath.endsWith("/")) {
+ path = path.substring(rootPath.length());
+ } else {
+ path = path.substring(rootPath.length() + 1);
+ }
+
+ // Encode the tag and path separately
+ path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
+ return new Uri.Builder().scheme("content")
+ .authority(mAuthority).encodedPath(path).build();
+ }
+
+ @Override
+ public File getFileForUri(Uri uri) {
+ String path = uri.getEncodedPath();
+
+ final int splitIndex = path.indexOf('/', 1);
+ final String tag = Uri.decode(path.substring(1, splitIndex));
+ path = Uri.decode(path.substring(splitIndex + 1));
+
+ final File root = mRoots.get(tag);
+ if (root == null) {
+ throw new IllegalArgumentException("Unable to find configured root for " + uri);
+ }
+
+ File file = new File(root, path);
+ try {
+ file = file.getCanonicalFile();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
+ }
+
+ if (!file.getPath().startsWith(root.getPath())) {
+ throw new SecurityException("Resolved path jumped beyond configured root");
+ }
+
+ return file;
+ }
+ }
+
+ /**
+ * Copied from ContentResolver.java
+ */
+ private static int modeToMode(String mode) {
+ int modeBits;
+ if ("r".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
+ } else if ("w".equals(mode) || "wt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else if ("wa".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_APPEND;
+ } else if ("rw".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE;
+ } else if ("rwt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else {
+ throw new IllegalArgumentException("Invalid mode: " + mode);
+ }
+ return modeBits;
+ }
+
+ private static File buildPath(File base, String... segments) {
+ File cur = base;
+ for (String segment : segments) {
+ if (segment != null) {
+ cur = new File(cur, segment);
+ }
+ }
+ return cur;
+ }
+
+ private static String[] copyOf(String[] original, int newLength) {
+ final String[] result = new String[newLength];
+ System.arraycopy(original, 0, result, 0, newLength);
+ return result;
+ }
+
+ private static Object[] copyOf(Object[] original, int newLength) {
+ final Object[] result = new Object[newLength];
+ System.arraycopy(original, 0, result, 0, newLength);
+ return result;
+ }
+}
+
diff --git a/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/MainActivity.java b/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/MainActivity.java
index 3fc18d1c..0077e7e0 100644
--- a/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/MainActivity.java
+++ b/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/MainActivity.java
@@ -83,6 +83,20 @@ public void onClick(View v) {
}
});
+ findViewById(R.id.btn_start_demo4).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 示例:直接通过宿主打开WebView插件中的Activity
+ // FIXME: 后续可以将webview MainActivity URL 改为动态传入
+ // 若没有安装,则直接提示“错误”
+ if (RePlugin.isPluginInstalled("webview")) {
+ RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("webview", "com.qihoo360.replugin.sample.webview.MainActivity"));
+ } else {
+ Toast.makeText(MainActivity.this, "You must install webview first!", Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
findViewById(R.id.btn_install_apk_from_assets).setOnClickListener(new View.OnClickListener() {
@Override
diff --git a/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/PluginFragmentActivity.java b/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/PluginFragmentActivity.java
index 327fcb9b..c398df9c 100644
--- a/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/PluginFragmentActivity.java
+++ b/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/PluginFragmentActivity.java
@@ -20,14 +20,28 @@ public class PluginFragmentActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ /**
+ * 注意:
+ *
+ * 如果一个插件是内置插件,那么这个插件的名字就是文件的前缀,比如:demo1.jar插件的名字就是demo1(host-gradle插件自动生成),可以执行诸如RePlugin.fetchClassLoader("demo1")的操作;
+ * 如果一个插件是外置插件,通过RePlugin.install("/sdcard/demo1.apk")安装的,则必须动态获取这个插件的名字来使用:
+ * PluginInfo pluginInfo = RePlugin.install("/sdcard/demo1.apk");
+ * RePlugin.preload(pluginInfo);//耗时
+ * String name = pluginInfo != null ? pluginInfo.getName() : null;
+ * ClassLoader classLoader = RePlugin.fetchClassLoader(name);
+ */
+
+ boolean isBuiltIn = true;
+ String pluginName = isBuiltIn ? "demo1" : "com.qihoo360.replugin.sample.demo1";
+
//注册相关Fragment的类
//注册一个全局Hook用于拦截系统对XX类的寻找定向到Demo1中的XX类主要是用于在xml中可以直接使用插件中的类
- RePlugin.registerHookingClass("com.qihoo360.replugin.sample.demo1.fragment.DemoFragment", RePlugin.createComponentName("demo1", "com.qihoo360.replugin.sample.demo1.fragment.DemoFragment"), null);
+ RePlugin.registerHookingClass("com.qihoo360.replugin.sample.demo1.fragment.DemoFragment", RePlugin.createComponentName(pluginName, "com.qihoo360.replugin.sample.demo1.fragment.DemoFragment"), null);
setContentView(R.layout.activity_plugin_fragment);
-
//代码使用插件Fragment
- ClassLoader d1ClassLoader = RePlugin.fetchClassLoader("demo1");//获取插件的ClassLoader
+ ClassLoader d1ClassLoader = RePlugin.fetchClassLoader(pluginName);//获取插件的ClassLoader
try {
Fragment fragment = d1ClassLoader.loadClass("com.qihoo360.replugin.sample.demo1.fragment.DemoCodeFragment").asSubclass(Fragment.class).newInstance();//使用插件的Classloader获取指定Fragment实例
getSupportFragmentManager().beginTransaction().add(R.id.container2, fragment).commit();//添加Fragment到UI
diff --git a/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/SampleApplication.java b/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/SampleApplication.java
index fc31be08..5294d67c 100644
--- a/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/SampleApplication.java
+++ b/replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/SampleApplication.java
@@ -62,6 +62,8 @@ protected RePluginConfig createConfig() {
// FIXME 若宿主为Release,则此处应加上您认为"合法"的插件的签名,例如,可以写上"宿主"自己的。
// RePlugin.addCertSignature("AAAAAAAAA");
+ // 在Art上,优化第一次loadDex的速度
+ // c.setOptimizeArtLoadDex(true);
return c;
}
@@ -90,6 +92,27 @@ public boolean onPluginNotExistsForActivity(Context context, String plugin, Inte
}
return super.onPluginNotExistsForActivity(context, plugin, intent, process);
}
+
+ /*
+ @Override
+ public PluginDexClassLoader createPluginClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
+ String odexName = pi.makeInstalledFileName() + ".dex";
+ if (RePlugin.getConfig().isOptimizeArtLoadDex()) {
+ Dex2OatUtils.injectLoadDex(dexPath, optimizedDirectory, odexName);
+ }
+
+ long being = System.currentTimeMillis();
+ PluginDexClassLoader pluginDexClassLoader = super.createPluginClassLoader(pi, dexPath, optimizedDirectory, librarySearchPath, parent);
+
+ if (BuildConfig.DEBUG) {
+ Log.d(Dex2OatUtils.TAG, "createPluginClassLoader use:" + (System.currentTimeMillis() - being));
+ String odexAbsolutePath = (optimizedDirectory + File.separator + odexName);
+ Log.d(Dex2OatUtils.TAG, "createPluginClassLoader odexSize:" + InterpretDex2OatHelper.getOdexSize(odexAbsolutePath));
+ }
+
+ return pluginDexClassLoader;
+ }
+ */
}
private class HostEventCallbacks extends RePluginEventCallbacks {
diff --git a/replugin-sample/host/app/src/main/res/layout/activity_main.xml b/replugin-sample/host/app/src/main/res/layout/activity_main.xml
index 9338a5aa..06b655f3 100644
--- a/replugin-sample/host/app/src/main/res/layout/activity_main.xml
+++ b/replugin-sample/host/app/src/main/res/layout/activity_main.xml
@@ -49,6 +49,13 @@
android:layout_gravity="center"
android:text="@string/startDemo3" />
+
+
Start plugin demo1Start Kotlin Plugin (demo3)
+ Start Plugin Webview (demo4) Start plugin demo1(forResult)Start plugin demo1(Fragment from demo1)Install or update external plugin
diff --git a/replugin-sample/host/app/src/main/res/xml/fileprovider_path.xml b/replugin-sample/host/app/src/main/res/xml/fileprovider_path.xml
new file mode 100644
index 00000000..537c69f2
--- /dev/null
+++ b/replugin-sample/host/app/src/main/res/xml/fileprovider_path.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/AndroidManifest.xml b/replugin-sample/plugin/plugin-demo1/app/src/main/AndroidManifest.xml
index 9f95239f..5e057c95 100644
--- a/replugin-sample/plugin/plugin-demo1/app/src/main/AndroidManifest.xml
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/AndroidManifest.xml
@@ -140,10 +140,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/MainActivity.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/MainActivity.java
index c6bd8b7c..d794c49c 100644
--- a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/MainActivity.java
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/MainActivity.java
@@ -38,6 +38,8 @@
import com.qihoo360.replugin.RePlugin;
import com.qihoo360.replugin.common.utils.TimeUtils;
+import com.qihoo360.replugin.sample.demo1.activity.file_provider.FileProviderActivity;
+import com.qihoo360.replugin.sample.demo1.activity.preference.PrefActivity2;
import com.qihoo360.replugin.sample.demo1.activity.single_instance.TIActivity1;
import com.qihoo360.replugin.sample.demo1.activity.single_top.SingleTopActivity1;
import com.qihoo360.replugin.sample.demo1.activity.standard.StandardActivity;
@@ -45,6 +47,7 @@
import com.qihoo360.replugin.sample.demo1.activity.theme.ThemeBlackNoTitleBarActivity;
import com.qihoo360.replugin.sample.demo1.activity.theme.ThemeBlackNoTitleBarFullscreenActivity;
import com.qihoo360.replugin.sample.demo1.activity.theme.ThemeDialogActivity;
+import com.qihoo360.replugin.sample.demo1.activity.webview.WebViewActivity;
import com.qihoo360.replugin.sample.demo1.service.PluginDemoService1;
import com.qihoo360.replugin.sample.demo2.IDemo2;
import com.qihoo360.replugin.sample.library.LibMainActivity;
@@ -77,6 +80,15 @@ private void initData() {
// =========
// Activity
// =========
+ mItems.add(new TestItem("Jump 2 Host", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 打开宿主Activity
+ Intent intent = new Intent();
+ intent.setClassName("com.qihoo360.replugin.sample.host", "com.qihoo360.replugin.sample.host.PluginFragmentActivity");
+ v.getContext().startActivity(intent);
+ }
+ }));
mItems.add(new TestItem("Activity: Standard", new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -323,6 +335,57 @@ public void onClick(View v) {
v.getContext().startActivity(intent);
}
}));
+
+ // dump
+ mItems.add(new TestItem("dump Detail", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+// // 打印RePlugin版本号
+// String version = RePlugin.getVersion();
+// Toast.makeText(MainActivity.this, "RePlugin v:" + version, Toast.LENGTH_SHORT).show();
+//
+// // dump详细的运行信息到PrintWriter
+// PrintWriter writer = null;
+// try {
+// writer = new PrintWriter("/sdcard/dump.txt");
+// RePlugin.dump(null, writer, null);
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// } finally {
+// if (writer != null) {
+// writer.close();
+// }
+// }
+ }
+ }));
+
+ // PreferenceActivity
+ mItems.add(new TestItem("Preference Activity", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(v.getContext(), PrefActivity2.class);
+ v.getContext().startActivity(intent);
+ }
+ }));
+
+ // WebView
+ mItems.add(new TestItem("WebView Activity", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(v.getContext(), WebViewActivity.class);
+ v.getContext().startActivity(intent);
+ }
+ }));
+
+ // FileProvider
+ mItems.add(new TestItem("FileProvider Activity", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(v.getContext(), FileProviderActivity.class);
+ v.getContext().startActivity(intent);
+ }
+ }));
}
private static final int REQUEST_CODE_DEMO2 = 0x021;
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/file_provider/BitmapUtils.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/file_provider/BitmapUtils.java
new file mode 100644
index 00000000..9376b37b
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/file_provider/BitmapUtils.java
@@ -0,0 +1,106 @@
+/*
+ * 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.activity.file_provider;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+/**
+ * Created by cundong on 2017/12/15.
+ *
+ * BitmapUtils
+ */
+public class BitmapUtils {
+
+ /**
+ * 从Resources中加载图片,得到合适的Bitmap
+ *
+ * @param res
+ * @param resId
+ * @param reqWidth
+ * @param reqHeight
+ * @return
+ */
+ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
+
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(res, resId, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeResource(res, resId, options);
+ }
+
+ /**
+ * 从SD卡上加载图片,得到合适的Bitmap
+ *
+ * @param pathName
+ * @param reqWidth
+ * @param reqHeight
+ * @return
+ */
+ public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) {
+
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(pathName, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeFile(pathName, options);
+ }
+
+ /**
+ * 计算图片的压缩比率
+ *
+ * @param options BitmapFactory.Options参数
+ * @param reqWidth 需要的宽度
+ * @param reqHeight 需要的高度
+ * @return
+ */
+ private static int calculateInSampleSize(
+ BitmapFactory.Options options, int reqWidth, int reqHeight) {
+ // Raw height and width of image
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > reqHeight || width > reqWidth) {
+
+ final int halfHeight = height / 2;
+ final int halfWidth = width / 2;
+
+ // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+ // height and width larger than the requested height and width.
+ while ((halfHeight / inSampleSize) > reqHeight
+ && (halfWidth / inSampleSize) > reqWidth) {
+ inSampleSize *= 2;
+ }
+ }
+
+ return inSampleSize;
+ }
+}
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/file_provider/FileProviderActivity.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/file_provider/FileProviderActivity.java
new file mode 100644
index 00000000..7350aee9
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/file_provider/FileProviderActivity.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.sample.demo1.activity.file_provider;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.qihoo360.replugin.sample.demo1.R;
+import com.qihoo360.replugin.sample.demo1.provider.FileProvider;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Created by cundong on 2017/12/15.
+ *
+ * 插件中使用 FileProvider 示例
+ */
+public class FileProviderActivity extends Activity implements View.OnClickListener {
+
+ private static final int WRITE_EXTERNAL_STORAGE_REQUEST_CODE = 1023;
+ private static final int REQUEST_TAKE_PHOTO = 1024;
+
+ // Host Provider Authorities
+ private static final String PROVIDER_AUTHORITIES = "com.qihoo360.replugin.sample.host.FILE_PROVIDER";
+
+ private Button mButton;
+ private ImageView mImageView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.simple_4);
+
+ mButton = (Button) findViewById(R.id.button);
+ mButton.setOnClickListener(this);
+
+ mImageView = (ImageView) findViewById(R.id.image);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mButton) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_REQUEST_CODE);
+ } else {
+ takePhoto();
+ }
+ } else {
+ takePhoto();
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ doNext(requestCode, grantResults);
+ }
+
+ private void doNext(int requestCode, int[] grantResults) {
+ if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ takePhoto();
+ } else {
+ Toast.makeText(this, "sd Permission Denied!", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ private void takePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+
+ Uri uri;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ uri = FileProvider.getUriForFile(this, PROVIDER_AUTHORITIES, getPhotoFile());
+ } else {
+ uri = Uri.fromFile(getPhotoFile());
+ }
+
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ startActivityForResult(intent, REQUEST_TAKE_PHOTO);
+ }
+
+ private File getPhotoFile() {
+ if (android.os.Environment.getExternalStorageState().equals(
+ android.os.Environment.MEDIA_MOUNTED)) {
+ File photoFile = new File(Environment.getExternalStorageDirectory(), "photo.jpg");
+ try {
+ photoFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return photoFile;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case REQUEST_TAKE_PHOTO:
+ if (resultCode == RESULT_OK) {
+
+ File photoFile = getPhotoFile();
+ if (photoFile != null && photoFile.exists() && photoFile.length() > 0) {
+ Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(photoFile.getPath(), 300, 300);
+ if (null != bitmap) {
+ Toast.makeText(this, "Get Photo Success.", Toast.LENGTH_SHORT).show();
+ mImageView.setImageBitmap(bitmap);
+ } else {
+ Toast.makeText(this, "Get Photo Fail.", Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ Toast.makeText(this, "Get Photo Fail.", Toast.LENGTH_SHORT).show();
+ }
+ }
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/preference/PrefActivity1.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/preference/PrefActivity1.java
new file mode 100644
index 00000000..b1d70cf5
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/preference/PrefActivity1.java
@@ -0,0 +1,57 @@
+/*
+ * 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.activity.preference;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+
+import com.qihoo360.replugin.sample.demo1.R;
+
+/**
+ * PreferenceActivity 示例1
+ *
+ * @author RePlugin Team
+ */
+public class PrefActivity1 extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.simple_2);
+
+ getFragmentManager().beginTransaction()
+ .replace(R.id.content, new TestPreferenceFragment())
+ .commit();
+ }
+
+ public static class TestPreferenceFragment extends PreferenceFragment {
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_headers);
+ }
+ }
+}
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/preference/PrefActivity2.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/preference/PrefActivity2.java
new file mode 100644
index 00000000..13c7024d
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/preference/PrefActivity2.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.sample.demo1.activity.preference;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+import com.qihoo360.replugin.sample.demo1.R;
+
+/**
+ * PreferenceActivity 示例2
+ *
+ * @author RePlugin Team
+ */
+public class PrefActivity2 extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_headers);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+
+ }
+}
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/webview/WebViewActivity.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/webview/WebViewActivity.java
new file mode 100644
index 00000000..ec14172a
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/activity/webview/WebViewActivity.java
@@ -0,0 +1,62 @@
+/*
+ * 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.activity.webview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.webkit.WebView;
+import android.widget.Toast;
+
+import com.qihoo360.replugin.sample.demo1.R;
+import com.qihoo360.replugin.sample.demo1.webview.WebPageProxy;
+
+/**
+ * @author RePlugin Team
+ *
+ * WebView 示例,具体业务逻辑由webView插件来实现
+ */
+public class WebViewActivity extends Activity {
+
+ static final String testUrl = "https://github.com/Qihoo360/RePlugin";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // 从WebView插件获取WebrViewPage的代理
+ WebPageProxy viewProxy = WebPageProxy.create(this);
+ if (viewProxy == null) {
+ Toast.makeText(this, " WebPageProxy create error!", Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+
+ View contentView = viewProxy.getView();
+ if (contentView == null) {
+ Toast.makeText(this, " WebPageProxy get View error!", Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+
+ setContentView(contentView);
+
+ WebView simpleWebPage = viewProxy.getWebView();
+ simpleWebPage.loadUrl(testUrl);
+
+ }
+}
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/provider/FileProvider.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/provider/FileProvider.java
new file mode 100644
index 00000000..d52d02c9
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/provider/FileProvider.java
@@ -0,0 +1,773 @@
+package com.qihoo360.replugin.sample.demo1.provider;
+
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.OpenableColumns;
+import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+/**
+ * FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing
+ * of files associated with an app by creating a content:// {@link Uri} for a file
+ * instead of a file:/// {@link Uri}.
+ *
+ * A content URI allows you to grant read and write access using
+ * temporary access permissions. When you create an {@link Intent} containing
+ * a content URI, in order to send the content URI
+ * to a client app, you can also call {@link Intent#setFlags(int) Intent.setFlags()} to add
+ * permissions. These permissions are available to the client app for as long as the stack for
+ * a receiving {@link android.app.Activity} is active. For an {@link Intent} going to a
+ * {@link android.app.Service}, the permissions are available as long as the
+ * {@link android.app.Service} is running.
+ *
+ * In comparison, to control access to a file:/// {@link Uri} you have to modify the
+ * file system permissions of the underlying file. The permissions you provide become available to
+ * any app, and remain in effect until you change them. This level of access is
+ * fundamentally insecure.
+ *
+ * The increased level of file access security offered by a content URI
+ * makes FileProvider a key part of Android's security infrastructure.
+ *
+ * This overview of FileProvider includes the following topics:
+ *
+ * Since the default functionality of FileProvider includes content URI generation for files, you
+ * don't need to define a subclass in code. Instead, you can include a FileProvider in your app
+ * by specifying it entirely in XML. To specify the FileProvider component itself, add a
+ * <provider>
+ * element to your app manifest. Set the android:name attribute to
+ * android.support.v4.content.FileProvider. Set the android:authorities
+ * attribute to a URI authority based on a domain you control; for example, if you control the
+ * domain mydomain.com you should use the authority
+ * com.mydomain.fileprovider. Set the android:exported attribute to
+ * false; the FileProvider does not need to be public. Set the
+ * android:grantUriPermissions attribute to true, to allow you
+ * to grant temporary access to files. For example:
+ *
+ * If you want to override any of the default behavior of FileProvider methods, extend
+ * the FileProvider class and use the fully-qualified class name in the android:name
+ * attribute of the <provider> element.
+ *
Specifying Available Files
+ * A FileProvider can only generate a content URI for files in directories that you specify
+ * beforehand. To specify a directory, specify the its storage area and path in XML, using child
+ * elements of the <paths> element.
+ * For example, the following paths element tells FileProvider that you intend to
+ * request content URIs for the images/ subdirectory of your private file area.
+ *
+ * The <paths> element must contain one or more of the following child elements:
+ *
+ *
+ *
+ *
+ * <files-path name="name" path="path" />
+ *
+ *
+ *
+ * Represents files in the files/ subdirectory of your app's internal storage
+ * area. This subdirectory is the same as the value returned by {@link Context#getFilesDir()
+ * Context.getFilesDir()}.
+ *
+ *
+ * <external-path name="name" path="path" />
+ *
+ *
+ *
+ * Represents files in the root of your app's external storage area. The path
+ * {@link Context#getExternalFilesDir(String) Context.getExternalFilesDir()} returns the
+ * files/ subdirectory of this this root.
+ *
+ *
+ *
+ * <cache-path name="name" path="path" />
+ *
+ *
+ *
+ * Represents files in the cache subdirectory of your app's internal storage area. The root path
+ * of this subdirectory is the same as the value returned by {@link Context#getCacheDir()
+ * getCacheDir()}.
+ *
+ *
+ *
+ * These child elements all use the same attributes:
+ *
+ *
+ *
+ * name="name"
+ *
+ *
+ * A URI path segment. To enforce security, this value hides the name of the subdirectory
+ * you're sharing. The subdirectory name for this value is contained in the
+ * path attribute.
+ *
+ *
+ * path="path"
+ *
+ *
+ * The subdirectory you're sharing. While the name attribute is a URI path
+ * segment, the path value is an actual subdirectory name. Notice that the
+ * value refers to a subdirectory, not an individual file or files. You can't
+ * share a single file by its file name, nor can you specify a subset of files using
+ * wildcards.
+ *
+ *
+ *
+ * You must specify a child element of <paths> for each directory that contains
+ * files for which you want content URIs. For example, these XML elements specify two directories:
+ *
+ * Put the <paths> element and its children in an XML file in your project.
+ * For example, you can add them to a new file called res/xml/file_paths.xml.
+ * To link this file to the FileProvider, add a
+ * <meta-data> element
+ * as a child of the <provider> element that defines the FileProvider. Set the
+ * <meta-data> element's "android:name" attribute to
+ * android.support.FILE_PROVIDER_PATHS. Set the element's "android:resource" attribute
+ * to @xml/file_paths (notice that you don't specify the .xml
+ * extension). For example:
+ *
+ * To share a file with another app using a content URI, your app has to generate the content URI.
+ * To generate the content URI, create a new {@link File} for the file, then pass the {@link File}
+ * to {@link #getUriForFile(Context, String, File) getUriForFile()}. You can send the content URI
+ * returned by {@link #getUriForFile(Context, String, File) getUriForFile()} to another app in an
+ * {@link Intent}. The client app that receives the content URI can open the file
+ * and access its contents by calling
+ * {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
+ * ContentResolver.openFileDescriptor} to get a {@link ParcelFileDescriptor}.
+ *
+ * For example, suppose your app is offering files to other apps with a FileProvider that has the
+ * authority com.mydomain.fileprovider. To get a content URI for the file
+ * default_image.jpg in the images/ subdirectory of your internal storage
+ * add the following code:
+ *
+ * File imagePath = new File(Context.getFilesDir(), "images");
+ * File newFile = new File(imagePath, "default_image.jpg");
+ * Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
+ *
+ * As a result of the previous snippet,
+ * {@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI
+ * content://com.mydomain.fileprovider/my_images/default_image.jpg.
+ *
Granting Temporary Permissions to a URI
+ * To grant an access permission to a content URI returned from
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}, do one of the following:
+ *
+ *
+ * Call the method
+ * {@link Context#grantUriPermission(String, Uri, int)
+ * Context.grantUriPermission(package, Uri, mode_flags)} for the content://
+ * {@link Uri}, using the desired mode flags. This grants temporary access permission for the
+ * content URI to the specified package, according to the value of the
+ * the mode_flags parameter, which you can set to
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}
+ * or both. The permission remains in effect until you revoke it by calling
+ * {@link Context#revokeUriPermission(Uri, int) revokeUriPermission()} or until the device
+ * reboots.
+ *
+ *
+ * Put the content URI in an {@link Intent} by calling {@link Intent#setData(Uri) setData()}.
+ *
+ *
+ * Next, call the method {@link Intent#setFlags(int) Intent.setFlags()} with either
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} or
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} or both.
+ *
+ *
+ * Finally, send the {@link Intent} to
+ * another app. Most often, you do this by calling
+ * {@link android.app.Activity#setResult(int, Intent) setResult()}.
+ *
+ * Permissions granted in an {@link Intent} remain in effect while the stack of the receiving
+ * {@link android.app.Activity} is active. When the stack finishes, the permissions are
+ * automatically removed. Permissions granted to one {@link android.app.Activity} in a client
+ * app are automatically extended to other components of that app.
+ *
+ *
+ *
+ *
Serving a Content URI to Another App
+ *
+ * There are a variety of ways to serve the content URI for a file to a client app. One common way
+ * is for the client app to start your app by calling
+ * {@link android.app.Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()},
+ * which sends an {@link Intent} to your app to start an {@link android.app.Activity} in your app.
+ * In response, your app can immediately return a content URI to the client app or present a user
+ * interface that allows the user to pick a file. In the latter case, once the user picks the file
+ * your app can return its content URI. In both cases, your app returns the content URI in an
+ * {@link Intent} sent via {@link android.app.Activity#setResult(int, Intent) setResult()}.
+ *
+ *
+ * You can also put the content URI in a {@link android.content.ClipData} object and then add the
+ * object to an {@link Intent} you send to a client app. To do this, call
+ * {@link Intent#setClipData(ClipData) Intent.setClipData()}. When you use this approach, you can
+ * add multiple {@link android.content.ClipData} objects to the {@link Intent}, each with its own
+ * content URI. When you call {@link Intent#setFlags(int) Intent.setFlags()} on the {@link Intent}
+ * to set temporary access permissions, the same permissions are applied to all of the content
+ * URIs.
+ *
+ *
+ * Note: The {@link Intent#setClipData(ClipData) Intent.setClipData()} method is
+ * only available in platform version 16 (Android 4.1) and later. If you want to maintain
+ * compatibility with previous versions, you should send one content URI at a time in the
+ * {@link Intent}. Set the action to {@link Intent#ACTION_SEND} and put the URI in data by calling
+ * {@link Intent#setData setData()}.
+ *
+ */
+public class FileProvider extends ContentProvider {
+ private static final String[] COLUMNS = {
+ OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
+
+ private static final String
+ META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS";
+
+ private static final String TAG_ROOT_PATH = "root-path";
+ private static final String TAG_FILES_PATH = "files-path";
+ private static final String TAG_CACHE_PATH = "cache-path";
+ private static final String TAG_EXTERNAL = "external-path";
+
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_PATH = "path";
+
+ private static final File DEVICE_ROOT = new File("/");
+
+ // @GuardedBy("sCache")
+ private static HashMap sCache = new HashMap();
+
+ private PathStrategy mStrategy;
+
+ /**
+ * The default FileProvider implementation does not need to be initialized. If you want to
+ * override this method, you must provide your own subclass of FileProvider.
+ */
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ /**
+ * After the FileProvider is instantiated, this method is called to provide the system with
+ * information about the provider.
+ *
+ * @param context A {@link Context} for the current component.
+ * @param info A {@link ProviderInfo} for the new provider.
+ */
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ super.attachInfo(context, info);
+
+ // Sanity check our security
+ if (info.exported) {
+ throw new SecurityException("Provider must not be exported");
+ }
+ if (!info.grantUriPermissions) {
+ throw new SecurityException("Provider must grant uri permissions");
+ }
+
+ mStrategy = getPathStrategy(context, info.authority);
+ }
+
+ /**
+ * Return a content URI for a given {@link File}. Specific temporary
+ * permissions for the content URI can be set with
+ * {@link Context#grantUriPermission(String, Uri, int)}, or added
+ * to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then
+ * {@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a
+ * content {@link Uri} for file paths defined in their <paths>
+ * meta-data element. See the Class Overview for more information.
+ *
+ * @param context A {@link Context} for the current component.
+ * @param authority The authority of a {@link FileProvider} defined in a
+ * {@code <provider>} element in your app's manifest.
+ * @param file A {@link File} pointing to the filename for which you want a
+ * content {@link Uri}.
+ * @return A content URI for the file.
+ * @throws IllegalArgumentException When the given {@link File} is outside
+ * the paths supported by the provider.
+ */
+ public static Uri getUriForFile(Context context, String authority, File file) {
+ final PathStrategy strategy = getPathStrategy(context, authority);
+ return strategy.getUriForFile(file);
+ }
+
+ /**
+ * Use a content URI returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file
+ * managed by the FileProvider.
+ * FileProvider reports the column names defined in {@link OpenableColumns}:
+ *
+ *
{@link OpenableColumns#DISPLAY_NAME}
+ *
{@link OpenableColumns#SIZE}
+ *
+ * For more information, see
+ * {@link ContentProvider#query(Uri, String[], String, String[], String)
+ * ContentProvider.query()}.
+ *
+ * @param uri A content URI returned by {@link #getUriForFile}.
+ * @param projection The list of columns to put into the {@link Cursor}. If null all columns are
+ * included.
+ * @param selection Selection criteria to apply. If null then all data that matches the content
+ * URI is returned.
+ * @param selectionArgs An array of {@link String}, containing arguments to bind to
+ * the selection parameter. The query method scans selection from left to
+ * right and iterates through selectionArgs, replacing the current "?" character in
+ * selection with the value at the current position in selectionArgs. The
+ * values are bound to selection as {@link String} values.
+ * @param sortOrder A {@link String} containing the column name(s) on which to sort
+ * the resulting {@link Cursor}.
+ * @return A {@link Cursor} containing the results of the query.
+ */
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ // ContentProvider has already checked granted permissions
+ final File file = mStrategy.getFileForUri(uri);
+
+ if (projection == null) {
+ projection = COLUMNS;
+ }
+
+ String[] cols = new String[projection.length];
+ Object[] values = new Object[projection.length];
+ int i = 0;
+ for (String col : projection) {
+ if (OpenableColumns.DISPLAY_NAME.equals(col)) {
+ cols[i] = OpenableColumns.DISPLAY_NAME;
+ values[i++] = file.getName();
+ } else if (OpenableColumns.SIZE.equals(col)) {
+ cols[i] = OpenableColumns.SIZE;
+ values[i++] = file.length();
+ }
+ }
+
+ cols = copyOf(cols, i);
+ values = copyOf(values, i);
+
+ final MatrixCursor cursor = new MatrixCursor(cols, 1);
+ cursor.addRow(values);
+ return cursor;
+ }
+
+ /**
+ * Returns the MIME type of a content URI returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}.
+ *
+ * @param uri A content URI returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}.
+ * @return If the associated file has an extension, the MIME type associated with that
+ * extension; otherwise application/octet-stream.
+ */
+ @Override
+ public String getType(Uri uri) {
+ // ContentProvider has already checked granted permissions
+ final File file = mStrategy.getFileForUri(uri);
+
+ final int lastDot = file.getName().lastIndexOf('.');
+ if (lastDot >= 0) {
+ final String extension = file.getName().substring(lastDot + 1);
+ final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mime != null) {
+ return mime;
+ }
+ }
+
+ return "application/octet-stream";
+ }
+
+ /**
+ * By default, this method throws an {@link UnsupportedOperationException}. You must
+ * subclass FileProvider if you want to provide different functionality.
+ */
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("No external inserts");
+ }
+
+ /**
+ * By default, this method throws an {@link UnsupportedOperationException}. You must
+ * subclass FileProvider if you want to provide different functionality.
+ */
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("No external updates");
+ }
+
+ /**
+ * Deletes the file associated with the specified content URI, as
+ * returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this
+ * method does not throw an {@link IOException}; you must check its return value.
+ *
+ * @param uri A content URI for a file, as returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}.
+ * @param selection Ignored. Set to {@code null}.
+ * @param selectionArgs Ignored. Set to {@code null}.
+ * @return 1 if the delete succeeds; otherwise, 0.
+ */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ // ContentProvider has already checked granted permissions
+ final File file = mStrategy.getFileForUri(uri);
+ return file.delete() ? 1 : 0;
+ }
+
+ /**
+ * By default, FileProvider automatically returns the
+ * {@link ParcelFileDescriptor} for a file associated with a content://
+ * {@link Uri}. To get the {@link ParcelFileDescriptor}, call
+ * {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
+ * ContentResolver.openFileDescriptor}.
+ *
+ * To override this method, you must provide your own subclass of FileProvider.
+ *
+ * @param uri A content URI associated with a file, as returned by
+ * {@link #getUriForFile(Context, String, File) getUriForFile()}.
+ * @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and
+ * write access, or "rwt" for read and write access that truncates any existing file.
+ * @return A new {@link ParcelFileDescriptor} with which you can access the file.
+ */
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ // ContentProvider has already checked granted permissions
+ final File file = mStrategy.getFileForUri(uri);
+ final int fileMode = modeToMode(mode);
+ return ParcelFileDescriptor.open(file, fileMode);
+ }
+
+ /**
+ * Return {@link FileProvider.PathStrategy} for given authority, either by parsing or
+ * returning from cache.
+ */
+ private static PathStrategy getPathStrategy(Context context, String authority) {
+ PathStrategy strat;
+ synchronized (sCache) {
+ strat = sCache.get(authority);
+ if (strat == null) {
+ try {
+ strat = parsePathStrategy(context, authority);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
+ } catch (XmlPullParserException e) {
+ throw new IllegalArgumentException(
+ "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
+ }
+ sCache.put(authority, strat);
+ }
+ }
+ return strat;
+ }
+
+ /**
+ * Parse and return {@link PathStrategy} for given authority as defined in
+ * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
+ *
+ * @see #getPathStrategy(Context, String)
+ */
+ private static PathStrategy parsePathStrategy(Context context, String authority)
+ throws IOException, XmlPullParserException {
+ final SimplePathStrategy strat = new SimplePathStrategy(authority);
+
+ final ProviderInfo info = context.getPackageManager()
+ .resolveContentProvider(authority, PackageManager.GET_META_DATA);
+ final XmlResourceParser in = info.loadXmlMetaData(
+ context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
+ if (in == null) {
+ throw new IllegalArgumentException(
+ "Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data");
+ }
+
+ int type;
+ while ((type = in.next()) != END_DOCUMENT) {
+ if (type == START_TAG) {
+ final String tag = in.getName();
+
+ final String name = in.getAttributeValue(null, ATTR_NAME);
+ String path = in.getAttributeValue(null, ATTR_PATH);
+
+ File target = null;
+ if (TAG_ROOT_PATH.equals(tag)) {
+ target = buildPath(DEVICE_ROOT, path);
+ } else if (TAG_FILES_PATH.equals(tag)) {
+ target = buildPath(context.getFilesDir(), path);
+ } else if (TAG_CACHE_PATH.equals(tag)) {
+ target = buildPath(context.getCacheDir(), path);
+ } else if (TAG_EXTERNAL.equals(tag)) {
+ target = buildPath(Environment.getExternalStorageDirectory(), path);
+ }
+
+ if (target != null) {
+ strat.addRoot(name, target);
+ }
+ }
+ }
+
+ return strat;
+ }
+
+ /**
+ * Strategy for mapping between {@link File} and {@link Uri}.
+ *
+ * Strategies must be symmetric so that mapping a {@link File} to a
+ * {@link Uri} and then back to a {@link File} points at the original
+ * target.
+ *
+ * Strategies must remain consistent across app launches, and not rely on
+ * dynamic state. This ensures that any generated {@link Uri} can still be
+ * resolved if your process is killed and later restarted.
+ *
+ * @see FileProvider.SimplePathStrategy
+ */
+ interface PathStrategy {
+ /**
+ * Return a {@link Uri} that represents the given {@link File}.
+ */
+ public Uri getUriForFile(File file);
+
+ /**
+ * Return a {@link File} that represents the given {@link Uri}.
+ */
+ public File getFileForUri(Uri uri);
+ }
+
+ /**
+ * Strategy that provides access to files living under a narrow whitelist of
+ * filesystem roots. It will throw {@link SecurityException} if callers try
+ * accessing files outside the configured roots.
+ *
+ * For example, if configured with
+ * {@code addRoot("myfiles", context.getFilesDir())}, then
+ * {@code context.getFileStreamPath("foo.txt")} would map to
+ * {@code content://myauthority/myfiles/foo.txt}.
+ */
+ static class SimplePathStrategy implements PathStrategy {
+ private final String mAuthority;
+ private final HashMap mRoots = new HashMap();
+
+ public SimplePathStrategy(String authority) {
+ mAuthority = authority;
+ }
+
+ /**
+ * Add a mapping from a name to a filesystem root. The provider only offers
+ * access to files that live under configured roots.
+ */
+ public void addRoot(String name, File root) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("Name must not be empty");
+ }
+
+ try {
+ // Resolve to canonical path to keep path checking fast
+ root = root.getCanonicalFile();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to resolve canonical path for " + root, e);
+ }
+
+ mRoots.put(name, root);
+ }
+
+ @Override
+ public Uri getUriForFile(File file) {
+ String path;
+ try {
+ path = file.getCanonicalPath();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
+ }
+
+ // Find the most-specific root path
+ Map.Entry mostSpecific = null;
+ for (Map.Entry root : mRoots.entrySet()) {
+ final String rootPath = root.getValue().getPath();
+ if (path.startsWith(rootPath) && (mostSpecific == null
+ || rootPath.length() > mostSpecific.getValue().getPath().length())) {
+ mostSpecific = root;
+ }
+ }
+
+ if (mostSpecific == null) {
+ throw new IllegalArgumentException(
+ "Failed to find configured root that contains " + path);
+ }
+
+ // Start at first char of path under root
+ final String rootPath = mostSpecific.getValue().getPath();
+ if (rootPath.endsWith("/")) {
+ path = path.substring(rootPath.length());
+ } else {
+ path = path.substring(rootPath.length() + 1);
+ }
+
+ // Encode the tag and path separately
+ path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
+ return new Uri.Builder().scheme("content")
+ .authority(mAuthority).encodedPath(path).build();
+ }
+
+ @Override
+ public File getFileForUri(Uri uri) {
+ String path = uri.getEncodedPath();
+
+ final int splitIndex = path.indexOf('/', 1);
+ final String tag = Uri.decode(path.substring(1, splitIndex));
+ path = Uri.decode(path.substring(splitIndex + 1));
+
+ final File root = mRoots.get(tag);
+ if (root == null) {
+ throw new IllegalArgumentException("Unable to find configured root for " + uri);
+ }
+
+ File file = new File(root, path);
+ try {
+ file = file.getCanonicalFile();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
+ }
+
+ if (!file.getPath().startsWith(root.getPath())) {
+ throw new SecurityException("Resolved path jumped beyond configured root");
+ }
+
+ return file;
+ }
+ }
+
+ /**
+ * Copied from ContentResolver.java
+ */
+ private static int modeToMode(String mode) {
+ int modeBits;
+ if ("r".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
+ } else if ("w".equals(mode) || "wt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else if ("wa".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_APPEND;
+ } else if ("rw".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE;
+ } else if ("rwt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else {
+ throw new IllegalArgumentException("Invalid mode: " + mode);
+ }
+ return modeBits;
+ }
+
+ private static File buildPath(File base, String... segments) {
+ File cur = base;
+ for (String segment : segments) {
+ if (segment != null) {
+ cur = new File(cur, segment);
+ }
+ }
+ return cur;
+ }
+
+ private static String[] copyOf(String[] original, int newLength) {
+ final String[] result = new String[newLength];
+ System.arraycopy(original, 0, result, 0, newLength);
+ return result;
+ }
+
+ private static Object[] copyOf(Object[] original, int newLength) {
+ final Object[] result = new Object[newLength];
+ System.arraycopy(original, 0, result, 0, newLength);
+ return result;
+ }
+}
+
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/IWebPage.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/IWebPage.java
new file mode 100644
index 00000000..0dbdbe5a
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/IWebPage.java
@@ -0,0 +1,32 @@
+/*
+ * 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.webview;
+
+import android.webkit.WebView;
+
+/**
+ * @author RePlugin Team
+ */
+public interface IWebPage {
+
+ /**
+ * 获取WebView对象
+ * @return
+ */
+ WebView getWebView();
+
+}
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/ViewProxy.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/ViewProxy.java
new file mode 100644
index 00000000..b16966e1
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/ViewProxy.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.sample.demo1.webview;
+
+import android.content.Context;
+import android.view.View;
+
+import com.qihoo360.replugin.RePlugin;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import static com.qihoo360.replugin.sample.demo1.BuildConfig.DEBUG;
+
+/**
+ * @author RePlugin Team
+ */
+public abstract class ViewProxy {
+
+ public static class Creator {
+
+ private final String mPlgName;
+
+ private final String mComponentName;
+
+ private Context mPlgContext;
+
+ private Class> mTargetClass;
+
+ private Constructor> mTargetConstructor;
+
+ public Creator(String pn, String cn) {
+ mPlgName = pn;
+ mComponentName = cn;
+ }
+
+ public boolean init() {
+ if (mPlgContext == null) {
+ mPlgContext = RePlugin.fetchContext(mPlgName);
+ if (mPlgContext == null) {
+ return false;
+ }
+ }
+ if (mTargetClass == null) {
+ mTargetClass = fetchClassByName(mPlgContext.getClassLoader(), mComponentName);
+ if (mTargetClass == null) {
+ return false;
+ }
+ }
+ if (mTargetConstructor == null) {
+ mTargetConstructor = fetchConstructorByName(Context.class);
+ if (mTargetConstructor == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Method fetchMethodByName(String name, Class>... pt) {
+ Method m;
+ try {
+ m = mTargetClass.getDeclaredMethod(name, pt);
+ } catch (Throwable e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ return m;
+ }
+
+ public View newViewInstance(Context c) {
+ if (mTargetConstructor == null) {
+ return null;
+ }
+ Object o;
+ try {
+ o = mTargetConstructor.newInstance(c);
+ } catch (Throwable e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ if (!(o instanceof View)) {
+ return null;
+ }
+ return (View) o;
+ }
+
+ private Constructor> fetchConstructorByName(Class>... pt) {
+ try {
+ return mTargetClass.getConstructor(pt);
+ } catch (NoSuchMethodException e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ }
+
+ private Class> fetchClassByName(ClassLoader cl, String cn) {
+ Class> clz;
+ try {
+ clz = cl.loadClass(cn);
+ } catch (Throwable e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ return clz;
+ }
+ }
+
+ protected V mView;
+
+ protected ViewProxy(V view) {
+ mView = view;
+ }
+
+ public V getView() {
+ return mView;
+ }
+
+ protected Object invoke(Method m, Object... args) {
+ if (m == null) {
+ return null;
+ }
+ try {
+ return m.invoke(mView, args);
+ } catch (Throwable e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ }
+}
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/WebPageProxy.java b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/WebPageProxy.java
new file mode 100644
index 00000000..8a7dc17b
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/java/com/qihoo360/replugin/sample/demo1/webview/WebPageProxy.java
@@ -0,0 +1,57 @@
+package com.qihoo360.replugin.sample.demo1.webview;
+
+import android.content.Context;
+import android.view.View;
+import android.webkit.WebView;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author RePlugin Team
+ */
+public class WebPageProxy extends ViewProxy implements IWebPage {
+
+ private static final String PLUGIN_NAME = "webview";
+
+ private static final String CLASS_NAME = "com.qihoo360.replugin.sample.webview.views.SimpleWebPage";
+
+ private static Method sGetWebViewMethod;
+
+ private static final ViewProxy.Creator CREATOR = new ViewProxy.Creator(PLUGIN_NAME, CLASS_NAME);
+
+ protected WebPageProxy(View view) {
+ super(view);
+ }
+
+ public static WebPageProxy create(Context c) {
+ boolean b = CREATOR.init();
+ if (!b) {
+ return null;
+ }
+ View v = CREATOR.newViewInstance(c);
+ return new WebPageProxy(v);
+ }
+
+ static WebPageProxy createByObject(View wv) {
+ boolean b = CREATOR.init();
+ if (!b) {
+ return null;
+ }
+ return new WebPageProxy(wv);
+ }
+
+ @Override
+ public WebView getWebView() {
+ if (sGetWebViewMethod == null) {
+ sGetWebViewMethod = CREATOR.fetchMethodByName("getWebView");
+ }
+ if (sGetWebViewMethod == null) {
+ return null;
+ }
+ Object obj = invoke(sGetWebViewMethod);
+ if (obj instanceof WebView) {
+ return (WebView) obj;
+ }
+ return null;
+ }
+}
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/res/layout/simple_2.xml b/replugin-sample/plugin/plugin-demo1/app/src/main/res/layout/simple_2.xml
new file mode 100644
index 00000000..6c8b239e
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/res/layout/simple_2.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/res/layout/simple_4.xml b/replugin-sample/plugin/plugin-demo1/app/src/main/res/layout/simple_4.xml
new file mode 100644
index 00000000..e812cd89
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/res/layout/simple_4.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/res/xml/fileprovider_path.xml b/replugin-sample/plugin/plugin-demo1/app/src/main/res/xml/fileprovider_path.xml
new file mode 100644
index 00000000..537c69f2
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/res/xml/fileprovider_path.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/app/src/main/res/xml/pref_headers.xml b/replugin-sample/plugin/plugin-demo1/app/src/main/res/xml/pref_headers.xml
new file mode 100644
index 00000000..bd180783
--- /dev/null
+++ b/replugin-sample/plugin/plugin-demo1/app/src/main/res/xml/pref_headers.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-demo1/build.gradle b/replugin-sample/plugin/plugin-demo1/build.gradle
index 1bc8e072..4a566e02 100644
--- a/replugin-sample/plugin/plugin-demo1/build.gradle
+++ b/replugin-sample/plugin/plugin-demo1/build.gradle
@@ -20,7 +20,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
- classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.1'
+ classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.2'
}
}
diff --git a/replugin-sample/plugin/plugin-demo2/build.gradle b/replugin-sample/plugin/plugin-demo2/build.gradle
index 1bc8e072..4a566e02 100644
--- a/replugin-sample/plugin/plugin-demo2/build.gradle
+++ b/replugin-sample/plugin/plugin-demo2/build.gradle
@@ -20,7 +20,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
- classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.1'
+ classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.2'
}
}
diff --git a/replugin-sample/plugin/plugin-webview/README.md b/replugin-sample/plugin/plugin-webview/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/replugin-sample/plugin/plugin-webview/app/build.gradle b/replugin-sample/plugin/plugin-webview/app/build.gradle
new file mode 100644
index 00000000..3600ff0a
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/build.gradle
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+/**
+ * 插件编译脚本
+ *
+ * @author RePlugin Team
+ */
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion '25.0.0'
+
+ defaultConfig {
+ versionName "1.1"
+ versionCode 100
+ targetSdkVersion 21
+ applicationId "com.qihoo360.replugin.sample.webview"
+ minSdkVersion 15
+ multiDexEnabled false
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+}
+
+// 这个plugin需要放在android配置之后,因为需要读取android中的配置项
+apply plugin: 'replugin-plugin-gradle'
+repluginPluginConfig {
+ pluginName = "webview"
+ hostApplicationId = "com.qihoo360.replugin.sample.host"
+ hostAppLauncherActivity = "com.qihoo360.replugin.sample.host.MainActivity"
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+ compile 'com.android.support:appcompat-v7:25.3.1'
+ compile 'com.qihoo360.replugin:replugin-plugin-lib:2.2.2'
+}
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-webview/app/proguard-rules.pro b/replugin-sample/plugin/plugin-webview/app/proguard-rules.pro
new file mode 100644
index 00000000..f508bbe8
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/proguard-rules.pro
@@ -0,0 +1,29 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\***\AppData\Local\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 *;
+#}
+
+# ---------------------------------------------
+-repackageclasses 'webview'
+-allowaccessmodification
+
+-renamesourcefileattribute webview
+-keepattributes SourceFile,LineNumberTable
+
+-keep class com.qihoo360.replugin.sample.webview.views.SimpleWebPage {
+ public *;
+}
+# ---------------------------------------------
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/AndroidManifest.xml b/replugin-sample/plugin/plugin-webview/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..c37e9176
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/AndroidManifest.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/MainActivity.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/MainActivity.java
new file mode 100644
index 00000000..8f180e94
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/MainActivity.java
@@ -0,0 +1,49 @@
+/*
+ * 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.webview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Window;
+import android.widget.LinearLayout;
+
+import com.qihoo360.replugin.sample.webview.views.SimpleWebPage;
+
+/**
+ * @author RePlugin Team
+ */
+public class MainActivity extends Activity {
+
+ private SimpleWebPage mWP;
+
+ static final String testUrl = "https://github.com/Qihoo360/RePlugin";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ setContentView(R.layout.webview);
+ LinearLayout rootView = (LinearLayout) findViewById(R.id.root);
+
+ // 添加webview视图
+ mWP = new SimpleWebPage(this);
+ rootView.addView(mWP);
+ mWP.getWebView().loadUrl(testUrl);
+ }
+
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/MainApp.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/MainApp.java
new file mode 100644
index 00000000..e82d5b9d
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/MainApp.java
@@ -0,0 +1,30 @@
+/*
+ * 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.webview;
+
+import android.app.Application;
+
+/**
+ * @author RePlugin Team
+ */
+public class MainApp extends Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebChromeClient.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebChromeClient.java
new file mode 100644
index 00000000..62ee517a
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebChromeClient.java
@@ -0,0 +1,67 @@
+/*
+ * 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.webview.common;
+
+import android.webkit.JsPromptResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+
+/**
+ * @author RePlugin Team
+ */
+public class CommonWebChromeClient extends WebChromeClient {
+
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ webview.injectJavascriptInterfaces();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.onProgressChanged(view, newProgress);
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ if (webview.handleJsInterface(view, url, message, defaultValue, result)) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return super.onJsPrompt(view, url, message, defaultValue, result);
+ }
+
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ webview.injectJavascriptInterfaces();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebView.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebView.java
new file mode 100644
index 00000000..fc6cd12e
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebView.java
@@ -0,0 +1,128 @@
+/*
+ * 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.webview.common;
+
+import android.content.Context;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.webkit.JsPromptResult;
+import android.webkit.WebView;
+
+import com.qihoo360.replugin.sample.webview.env.Env;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * @author RePlugin Team
+ */
+public class CommonWebView extends WebView {
+
+ public CommonWebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public CommonWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public CommonWebView(Context context) {
+ super(context);
+ init();
+ }
+
+ private void init() {
+ removeSearchBoxImpl();
+ }
+
+ private boolean removeSearchBoxImpl() {
+ try {
+ if (Build.VERSION.SDK_INT >= 11 && !(Build.VERSION.SDK_INT >= 17)) {
+ invokeMethod("removeJavascriptInterface", "searchBoxJavaBridge_");
+ return true;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ private void invokeMethod(String method, String param) {
+ Method m;
+ try {
+ m = WebView.class.getDeclaredMethod(method, String.class);
+ m.setAccessible(true);
+ m.invoke(this, param);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void addJavascriptInterface(Object obj, String interfaceName) {
+ if (TextUtils.isEmpty(interfaceName)) {
+ return;
+ }
+
+ injectJavascriptInterfaces();
+ }
+
+ public boolean handleJsInterface(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
+ try {
+ // 此处添加对message的解析处理逻辑等
+ result.cancel();
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ result.cancel();
+ return false;
+ }
+
+ public void injectJavascriptInterfaces() {
+ String jsStringCache = genJavascriptInterfacesString();
+ try {
+ if (!TextUtils.isEmpty(jsStringCache)) {
+ this.loadUrl(jsStringCache);
+ }
+ } catch (Exception e) {
+ if (Env.DEBUG) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ private String genJavascriptInterfacesString() {
+ // 此处添加自己具体要注入的 JS 代码段
+ StringBuilder script = new StringBuilder();
+ script.append("javascript:(function JsAddJavascriptInterface_(){");
+ // add ...
+ script.append("})()");
+
+ return script.toString();
+ }
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebViewClient.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebViewClient.java
new file mode 100644
index 00000000..dd760c77
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/common/CommonWebViewClient.java
@@ -0,0 +1,108 @@
+/*
+ * 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.webview.common;
+
+import android.graphics.Bitmap;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+/**
+ * @author RePlugin Team
+ */
+public class CommonWebViewClient extends WebViewClient {
+
+ @Override
+ public void onLoadResource(WebView view, String url) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ webview.injectJavascriptInterfaces();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.onLoadResource(view, url);
+ }
+
+ @Override
+ public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ webview.injectJavascriptInterfaces();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.doUpdateVisitedHistory(view, url, isReload);
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ webview.injectJavascriptInterfaces();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.onPageStarted(view, url, favicon);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ webview.injectJavascriptInterfaces();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.onPageFinished(view, url);
+ }
+
+ @Override
+ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ webview.injectJavascriptInterfaces();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.onReceivedError(view, errorCode, description, failingUrl);
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ try {
+ if (view instanceof CommonWebView) {
+ CommonWebView webview = (CommonWebView) view;
+ webview.injectJavascriptInterfaces();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.shouldOverrideUrlLoading(view, url);
+ // 默认不调用第三方浏览器
+ return false;
+ }
+
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/env/Env.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/env/Env.java
new file mode 100644
index 00000000..efa22b36
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/env/Env.java
@@ -0,0 +1,44 @@
+/*
+ * 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.webview.env;
+
+import android.os.Build;
+import android.webkit.WebView;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author RePlugin Team
+ */
+public class Env {
+ public static final boolean DEBUG = true;
+ public static final String TAG = "webview_demo";
+
+ private static void setWebContentsDebuggingEnabled(boolean b) {
+ if (Build.VERSION.SDK_INT < 19) {
+ return;
+ }
+ try {
+ Method m = WebView.class.getMethod("setWebContentsDebuggingEnabled", boolean.class);
+ m.invoke(null, b);
+ } catch (Exception e) {
+ if (Env.DEBUG) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/utils/ReflectUtil.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/utils/ReflectUtil.java
new file mode 100644
index 00000000..225427ff
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/utils/ReflectUtil.java
@@ -0,0 +1,114 @@
+/*
+ * 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.webview.utils;
+
+import android.util.Log;
+
+import com.qihoo360.replugin.sample.webview.env.Env;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * @author RePlugin Team
+ */
+public class ReflectUtil {
+ private static final String TAG = "ReflectUtil";
+
+ public static Object invokeStaticMethod(String clzName, String methodName, Class>[] methodParamTypes, Object... methodParamValues) {
+ try {
+ Class clz = Class.forName(clzName);
+ if (clz != null) {
+ Method med = clz.getDeclaredMethod(methodName, methodParamTypes);
+ if (med != null) {
+ med.setAccessible(true);
+ Object retObj = med.invoke(null, methodParamValues);
+ return retObj;
+ }
+ }
+ } catch (Exception e) {
+ if (Env.DEBUG) {
+ Log.e(TAG, "invokeStaticMethod got Exception:", e);
+ }
+ }
+ return null;
+ }
+
+ public static Object invokeMethod(String clzName, String methodName, Object methodReceiver, Class>[] methodParamTypes, Object... methodParamValues) {
+ try {
+ if (methodReceiver == null) {
+ return null;
+ }
+
+ Class clz = Class.forName(clzName);
+ if (clz != null) {
+ Method med = clz.getDeclaredMethod(methodName, methodParamTypes);
+ if (med != null) {
+ med.setAccessible(true);
+ Object retObj = med.invoke(methodReceiver, methodParamValues);
+ return retObj;
+ }
+ }
+ } catch (Exception e) {
+ if (Env.DEBUG) {
+ Log.e(TAG, "invokeStaticMethod got Exception:", e);
+ }
+ }
+ return null;
+ }
+
+ public static final Object getStaticField(String clzName, String filedName) {
+ try {
+ Field field = null;
+ Class> clz = Class.forName(clzName);
+ if (clz != null) {
+ field = clz.getField(filedName);
+ if (field != null) {
+ return field.get("");
+ }
+ }
+ } catch (Exception e) {
+ if (Env.DEBUG) {
+ Log.e(TAG, "getStaticField got Exception:", e);
+ }
+ }
+
+ return null;
+ }
+
+ public static final Object getField(String clzName, Object obj, String filedName) {
+ try {
+ if (obj == null) {
+ return null;
+ }
+
+ Class> clz = Class.forName(clzName);
+ if (clz != null) {
+ Field field = clz.getField(filedName);
+ if (field != null) {
+ return field.get(obj);
+ }
+ }
+ } catch (Exception e) {
+ if (Env.DEBUG) {
+ Log.e(TAG, "getStaticField got Exception:", e);
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/utils/WebViewResourceHelper.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/utils/WebViewResourceHelper.java
new file mode 100644
index 00000000..6e4aa7c5
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/utils/WebViewResourceHelper.java
@@ -0,0 +1,156 @@
+/*
+ * 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.webview.utils;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.qihoo360.replugin.sample.webview.env.Env;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author RePlugin Team
+ */
+public class WebViewResourceHelper {
+ private static boolean sInitialed = false;
+
+ public static boolean addChromeResourceIfNeeded(Context context) {
+ if (sInitialed) {
+ return true;
+ }
+
+ String dir = getWebViewResourceDir(context);
+ if (TextUtils.isEmpty(dir)) {
+ return false;
+ }
+
+ try {
+ Method m = getAddAssetPathMethod();
+ if (m != null) {
+ int ret = (int) m.invoke(context.getAssets(), dir);
+ sInitialed = ret > 0;
+ return sInitialed;
+ }
+ } catch (Exception e) {
+ if (Env.DEBUG) {
+ Log.d(Env.TAG, "[init webview res] : invoke method error ! ", e);
+ }
+ }
+
+ return false;
+ }
+
+ private static Method getAddAssetPathMethod() {
+ Method m = null;
+ Class c = AssetManager.class;
+
+ if (Build.VERSION.SDK_INT >= 24) {
+ try {
+ m = c.getDeclaredMethod("addAssetPathAsSharedLibrary", String.class);
+ m.setAccessible(true);
+ } catch (NoSuchMethodException e) {
+ // Do Nothing
+ e.printStackTrace();
+ }
+ return m;
+ }
+
+ try {
+ m = c.getDeclaredMethod("addAssetPath", String.class);
+ m.setAccessible(true);
+ } catch (NoSuchMethodException e) {
+ // Do Nothing
+ e.printStackTrace();
+ }
+
+ return m;
+ }
+
+ private static String getWebViewResourceDir(Context context) {
+ String pkgName = getWebViewPackageName();
+ if (TextUtils.isEmpty(pkgName)) {
+ return null;
+ }
+
+ try {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(getWebViewPackageName(), PackageManager.GET_SHARED_LIBRARY_FILES);
+ return pi.applicationInfo.sourceDir;
+ } catch (PackageManager.NameNotFoundException e) {
+ if (Env.DEBUG) {
+ Log.e(Env.TAG, "get webview application info failed! ", e);
+ }
+ } catch (Exception e) {
+ // Do Nothing
+ }
+
+ return null;
+ }
+
+ private static String getWebViewPackageName() {
+ int sdk = Build.VERSION.SDK_INT;
+
+ if (sdk <= 20) {
+ return null;
+ }
+
+ switch (sdk) {
+ case 21:
+ case 22:
+ return getWebViewPackageName4Lollipop();
+ case 23:
+ return getWebViewPackageName4M();
+ case 24:
+ return getWebViewPackageName4N();
+ case 25:
+ default:
+ return getWebViewPackageName4More();
+ }
+ }
+
+ private static String getWebViewPackageName4Lollipop() {
+ try {
+ return (String) ReflectUtil.invokeStaticMethod("android.webkit.WebViewFactory", "getWebViewPackageName", null);
+ } catch (Throwable e) {
+ //
+ }
+ return "com.google.android.webview";
+ }
+
+ private static String getWebViewPackageName4M() {
+ return getWebViewPackageName4Lollipop();
+ }
+
+ private static String getWebViewPackageName4N() {
+ try {
+ Context c = (Context) ReflectUtil.invokeStaticMethod("android.webkit.WebViewFactory", "getWebViewContextAndSetProvider", null);
+ return c.getApplicationInfo().packageName;
+ } catch (Throwable e) {
+ //
+ }
+ return "com.google.android.webview";
+ }
+
+ private static String getWebViewPackageName4More() {
+ return getWebViewPackageName4N();
+ }
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/views/SimpleWebPage.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/views/SimpleWebPage.java
new file mode 100644
index 00000000..24b9020e
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/views/SimpleWebPage.java
@@ -0,0 +1,118 @@
+/*
+ * 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.webview.views;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.webkit.CookieManager;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.widget.RelativeLayout;
+
+import com.qihoo360.replugin.sample.webview.common.CommonWebChromeClient;
+import com.qihoo360.replugin.sample.webview.common.CommonWebViewClient;
+import com.qihoo360.replugin.sample.webview.env.Env;
+
+import static android.webkit.WebView.setWebContentsDebuggingEnabled;
+
+/**
+ * @author RePlugin Team
+ */
+public class SimpleWebPage extends RelativeLayout {
+
+ // 使用者提供的Context
+ private Context mUserContext;
+ private SimpleWebView mWebView;
+
+ public SimpleWebPage(Context context) {
+ super(context.getApplicationContext());
+ mUserContext = context;
+ init();
+ }
+
+ public SimpleWebPage(Context context, AttributeSet attrs) {
+ super(context.getApplicationContext(), attrs);
+ mUserContext = context;
+ init();
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ public void init() {
+ if (Env.DEBUG) {
+ setWebContentsDebuggingEnabled(true);
+ }
+ RelativeLayout.LayoutParams rootView = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT);
+ // 将WebView加到布局中
+ mWebView = new SimpleWebView(mUserContext);
+ // Android 7.0以上的webview不设置背景(默认背景应该是透明的),渲染有问题,有明显的卡顿
+ mWebView.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
+ addView(mWebView, rootView);
+ // 辅助处理各种通知、请求事件,如果不设置WebViewClient,请求会跳转系统浏览器
+ mWebView.setWebViewClient(new CommonWebViewClient());
+ // 辅助处理JavaScript、页喧解析渲染、页面标题、加载进度等等
+ mWebView.setWebChromeClient(new CommonWebChromeClient());
+
+ WebSettings ws = mWebView.getSettings();
+
+ // 允许缩放
+ ws.setBuiltInZoomControls(true);
+ //默认不显示ZoomButton,否则会有windowLeaked警告
+ if (Build.VERSION.SDK_INT > 10) {
+ ws.setDisplayZoomControls(false);
+ }
+ // 设置是否允许WebView使用JavaScript
+ ws.setJavaScriptEnabled(true);
+ // 启动地理定位
+ ws.setGeolocationEnabled(true);
+ // 开启DomStorage缓存
+ ws.setDomStorageEnabled(true);
+ ws.setJavaScriptCanOpenWindowsAutomatically(true);
+ ws.setAppCacheEnabled(false);
+ // 防止WebView跨源攻击
+ // 设置是否允许WebView使用File协议,默认值是允许
+ // 注意:不允许使用File协议,则不会存在通过file协议的跨源安全威胁,但同时也限制了WebView的功能,使其不能加载本地的HTML文件
+ ws.setAllowFileAccess(false);
+ if (Build.VERSION.SDK_INT >= 16) {
+ // 设置是否允许通过file url加载的Javascript读取其他的本地文件
+ ws.setAllowFileAccessFromFileURLs(false);
+ // 设置是否允许通过file url加载的Javascript可以访问其他的源,包括其他的文件和http,https等其他的源
+ ws.setAllowUniversalAccessFromFileURLs(false);
+ }
+ // 防止个人敏感数据泄漏
+ ws.setSavePassword(false);
+ ws.setSaveFormData(false);
+ ws.setUserAgentString(mWebView.getUserAgentEx());
+ if (Build.VERSION.SDK_INT >= 21) {
+ ws.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
+ CookieManager.getInstance().acceptThirdPartyCookies(mWebView);
+ }
+ if (mUserContext instanceof Activity) {
+ // 此处添加自定义的JS接口
+// mWebView.addJavascriptInterface(...);
+ }
+ }
+
+ public WebView getWebView() {
+ return mWebView;
+ }
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/views/SimpleWebView.java b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/views/SimpleWebView.java
new file mode 100644
index 00000000..2704c457
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/java/com/qihoo360/replugin/sample/webview/views/SimpleWebView.java
@@ -0,0 +1,64 @@
+/*
+ * 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.webview.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+
+import com.qihoo360.replugin.sample.webview.utils.WebViewResourceHelper;
+
+/**
+ * @author RePlugin Team
+ */
+public class SimpleWebView extends WebView {
+
+ private static String sUserAgent;
+
+ public SimpleWebView(Context context) {
+ this(context, null);
+ }
+
+ public SimpleWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ // webview 插件化后对资源的统一处理
+ WebViewResourceHelper.addChromeResourceIfNeeded(context);
+ }
+
+ public SimpleWebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ // webview 插件化后对资源的统一处理
+ WebViewResourceHelper.addChromeResourceIfNeeded(context);
+ }
+
+ public String getUserAgentEx() {
+ if (sUserAgent == null) {
+ WebSettings ws = getSettings();
+ sUserAgent = ws.getUserAgentString();
+ }
+
+ // 此处可自定义自己的UserAgent
+ return sUserAgent;
+ }
+
+ @Override
+ public void loadUrl(String url) {
+ // 此处可“种植”Cookie(Q&T)
+ super.loadUrl(url);
+ }
+}
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/res/drawable/ic_launcher.png b/replugin-sample/plugin/plugin-webview/app/src/main/res/drawable/ic_launcher.png
new file mode 100644
index 00000000..bfa42f0e
Binary files /dev/null and b/replugin-sample/plugin/plugin-webview/app/src/main/res/drawable/ic_launcher.png differ
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/res/layout/web_page.xml b/replugin-sample/plugin/plugin-webview/app/src/main/res/layout/web_page.xml
new file mode 100644
index 00000000..e119ba87
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/res/layout/web_page.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/res/layout/webview.xml b/replugin-sample/plugin/plugin-webview/app/src/main/res/layout/webview.xml
new file mode 100644
index 00000000..252fab80
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/res/layout/webview.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/res/values/colors.xml b/replugin-sample/plugin/plugin-webview/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..ab323d4a
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/res/values/colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-webview/app/src/main/res/values/strings.xml b/replugin-sample/plugin/plugin-webview/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..e1a9a6b6
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/app/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ WebView
+
diff --git a/replugin-sample/plugin/plugin-webview/build.gradle b/replugin-sample/plugin/plugin-webview/build.gradle
new file mode 100644
index 00000000..4a566e02
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+buildscript {
+ repositories {
+ mavenLocal()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.3'
+ classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.2'
+ }
+}
+
+allprojects {
+ repositories {
+ mavenLocal()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/replugin-sample/plugin/plugin-webview/gradle.properties b/replugin-sample/plugin/plugin-webview/gradle.properties
new file mode 100644
index 00000000..ba072bc6
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/gradle.properties
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# 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
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/replugin-sample/plugin/plugin-webview/gradle/wrapper/gradle-wrapper.properties b/replugin-sample/plugin/plugin-webview/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..99639ba7
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Jul 05 11:57:44 CST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/replugin-sample/plugin/plugin-webview/gradlew b/replugin-sample/plugin/plugin-webview/gradlew
new file mode 100755
index 00000000..9d82f789
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/replugin-sample/plugin/plugin-webview/gradlew.bat b/replugin-sample/plugin/plugin-webview/gradlew.bat
new file mode 100755
index 00000000..8a0b282a
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/replugin-sample/plugin/plugin-webview/settings.gradle b/replugin-sample/plugin/plugin-webview/settings.gradle
new file mode 100644
index 00000000..a1101692
--- /dev/null
+++ b/replugin-sample/plugin/plugin-webview/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+include ':app'