diff --git a/tinker-android/tinker-android-anno/src/main/resources/TinkerAnnoApplication.tmpl b/tinker-android/tinker-android-anno/src/main/resources/TinkerAnnoApplication.tmpl index 9eeaa4ccc..454c0cf04 100644 --- a/tinker-android/tinker-android-anno/src/main/resources/TinkerAnnoApplication.tmpl +++ b/tinker-android/tinker-android-anno/src/main/resources/TinkerAnnoApplication.tmpl @@ -1,5 +1,10 @@ package %PACKAGE%; +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; + +import com.tencent.tinker.loader.app.ApplicationLike; import com.tencent.tinker.loader.app.TinkerApplication; /** @@ -13,4 +18,32 @@ public class %APPLICATION% extends TinkerApplication { super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%, %TINKER_USE_DLC%, %TINKER_USE_INTERPRET_MODE_ON_SUPPORTED_32BIT_SYSTEM%); } + @Override + public Resources getResources() { + final Resources resources = super.getResources(); + final ApplicationLike applicationLike = getApplicationLike(); + return (applicationLike == null) ? resources : applicationLike.getResources(resources); + } + + @Override + public AssetManager getAssets() { + final AssetManager assets = super.getAssets(); + final ApplicationLike applicationLike = getApplicationLike(); + return (applicationLike == null) ? assets : applicationLike.getAssets(assets); + } + + @Override + public Context getBaseContext() { + final Context base = super.getBaseContext(); + final ApplicationLike applicationLike = getApplicationLike(); + return (applicationLike == null) ? base : applicationLike.getBaseContext(base); + } + + @Override + public ClassLoader getClassLoader() { + final ClassLoader classLoader = super.getClassLoader(); + final ApplicationLike applicationLike = getApplicationLike(); + return (applicationLike == null) ? classLoader : applicationLike.getClassLoader(classLoader); + } + } \ No newline at end of file diff --git a/tinker-android/tinker-android-anno/src/test/java/com/tencent/tinker/anno/test/AnnotationProcessorTest.java b/tinker-android/tinker-android-anno/src/test/java/com/tencent/tinker/anno/test/AnnotationProcessorTest.java new file mode 100644 index 000000000..69ca2e583 --- /dev/null +++ b/tinker-android/tinker-android-anno/src/test/java/com/tencent/tinker/anno/test/AnnotationProcessorTest.java @@ -0,0 +1,95 @@ +package com.tencent.tinker.anno.test; + +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class AnnotationProcessorTest { + + private static final String TEMPLATE_RESOURCE = "TinkerAnnoApplication.tmpl"; + + private static final String[] HOT_PATH_METHODS = { + "getResources", + "getAssets", + "getClassLoader" + }; + + private String readTemplate() throws IOException { + InputStream is = getClass().getClassLoader().getResourceAsStream(TEMPLATE_RESOURCE); + if (is == null) { + is = AnnotationProcessorTest.class.getResourceAsStream("/" + TEMPLATE_RESOURCE); + } + assertNotNull("Template " + TEMPLATE_RESOURCE + " must be on the test classpath", is); + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int n; + while ((n = is.read(buf)) > 0) { + out.write(buf, 0, n); + } + return new String(out.toByteArray(), "UTF-8"); + } finally { + is.close(); + } + } + + private String extractMethodBody(String src, String methodName) { + Pattern p = Pattern.compile("\\b" + Pattern.quote(methodName) + "\\s*\\([^)]*\\)\\s*\\{"); + Matcher m = p.matcher(src); + if (!m.find()) { + return null; + } + int start = m.end() - 1; + int depth = 0; + for (int i = start; i < src.length(); i++) { + char c = src.charAt(i); + if (c == '{') { + depth++; + } else if (c == '}') { + depth--; + if (depth == 0) { + return src.substring(start, i + 1); + } + } + } + return null; + } + + @Test + public void hotPathMethods_useDirectDelegation_notReflection() throws IOException { + String template = readTemplate(); + for (String method : HOT_PATH_METHODS) { + String body = extractMethodBody(template, method); + assertNotNull("Template should override " + method + "()", body); + assertFalse( + method + "() must call ApplicationLike directly, not via Method.invoke", + body.contains(".invoke(") + ); + assertTrue( + method + "() should delegate to the ApplicationLike instance", + body.contains(method + "(") + ); + } + } + + @Test + public void template_doesNotRetainReflectionCachesForHotPath() throws IOException { + String template = readTemplate(); + for (String method : HOT_PATH_METHODS) { + String cacheField = "mCurrentMethod_" + method; + assertFalse( + "Reflection Method cache field for hot path method " + method + + " should be removed from the template", + template.contains(cacheField) + ); + } + } +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/app/ApplicationLike.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/app/ApplicationLike.java new file mode 100644 index 000000000..97a5ffc8e --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/app/ApplicationLike.java @@ -0,0 +1,108 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.tinker.lib.app; + +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; + +public abstract class ApplicationLike implements ApplicationLifeCycle { + private final Application application; + private final int tinkerFlags; + private final boolean tinkerLoadVerifyFlag; + private final long applicationStartElapsedTime; + private final long applicationStartMillisTime; + private Intent tinkerResultIntent; + + public ApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, + long applicationStartElapsedTime, long applicationStartMillisTime, + Intent tinkerResultIntent) { + this.application = application; + this.tinkerFlags = tinkerFlags; + this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; + this.applicationStartElapsedTime = applicationStartElapsedTime; + this.applicationStartMillisTime = applicationStartMillisTime; + this.tinkerResultIntent = tinkerResultIntent; + } + + public final Application getApplication() { + return application; + } + + public final int getTinkerFlags() { + return tinkerFlags; + } + + public final boolean getTinkerLoadVerifyFlag() { + return tinkerLoadVerifyFlag; + } + + public final long getApplicationStartElapsedTime() { + return applicationStartElapsedTime; + } + + public final long getApplicationStartMillisTime() { + return applicationStartMillisTime; + } + + public final Intent getTinkerResultIntent() { + return tinkerResultIntent; + } + + public Resources getResources(Resources resources) { + return resources; + } + + public AssetManager getAssets(AssetManager assets) { + return assets; + } + + public Context getBaseContext(Context base) { + return base; + } + + public ClassLoader getClassLoader(ClassLoader cl) { + return cl; + } + + @Override + public void onCreate() { + } + + @Override + public void onTerminate() { + } + + @Override + public void onLowMemory() { + } + + @Override + public void onTrimMemory(int level) { + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + } + + @Override + public void onBaseContextAttached(Context base) { + } +}