Tinker 소스 분석 제품군 (1) - Application 프록시 메커니즘
인용문
지난번 안드로이드 열복원 원리의 탐구와 실천에서 알 수 있듯이 팅커가 열복원을 실현하는 원리는 자신의 전량patch 가방을 dexElements 그룹의 앞부분에 삽입하여 열복원의 목적을 달성하는 것이다. 이 익숙하지 않은 어린이신발에 대해 나의 이전 블로그를 뒤적일 수 있다.
다음 모든 소스 분석은 Tinker 1.7.7 버전을 기준으로 합니다.
Application 프록시 메커니즘
일반적으로 우리는 Application에서 tinker의 초기화를 포함하여 초기화 작업을 진행한다. 그러면 응용 프로그램에서 언급된 클래스는 tinker의 초기화가 완료되기 전에 클래스 마운트에 불러왔다. 그러면 우리가 말한 패치 dex 패키지를 dex Elements 그룹의 앞부분에 삽입하는 방법은 작용하지 않는다.Tinker는 어떻게 이 문제를 해결하여 Application에서 사용되는 종류를 복구할 수 있습니까?
tinker의 공식 접속 문서를 본 학우들은 tinker가 우리에게 자신의 Application을 계승하는 것을 추천한다
ApplicationLike
. 그러면 우리의 착안점은 바로 여기다. 우리는 먼저 ApplicationLike
의 원본을 보자(원본이 비교적 길고 관건적인 부분만 붙인다. 아래와 같다)public abstract class ApplicationLike implements ApplicationLifeCycle {
private final Application application;
private final Intent tinkerResultIntent;
private final long applicationStartElapsedTime;
private final long applicationStartMillisTime;
private final int tinkerFlags;
private final boolean tinkerLoadVerifyFlag;
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;
}
...
}
이를 통해 알 수 있듯이
ApplicationLike
는 진정한 의미의 안드로이드Application
가 아니다. 왜냐하면 그는 Application
를 계승하지 않았기 때문이다ApplicationLifeCycle
. 단지 하나의 일반적인 클래스가 실현했기 때문이다ApplicationLifeCycle
. 이 인터페이스는 우리가 먼저 Application
이 인터페이스의 원본 코드를 살펴보자.public interface ApplicationLifeCycle {
/**
* Same as {@link Application#onCreate()}.
*/
void onCreate();
/**
* Same as {@link Application#onLowMemory()}.
*/
void onLowMemory();
/**
* Same as {@link Application#onTrimMemory(int level)}.
* @param level
*/
void onTrimMemory(int level);
/**
* Same as {@link Application#onTerminate()}.
*/
void onTerminate();
/**
* Same as {@link Application#onConfigurationChanged(Configuration newconfig)}.
*/
void onConfigurationChanged(Configuration newConfig);
/**
* Same as {@link Application#attachBaseContext(Context context)}.
*/
void onBaseContextAttached(Context base);
}
이 인터페이스 안의 몇 가지 방법은 당신이 분명히 낯이 익을 거라고 믿습니다. 그렇습니다. 이 몇 가지 방법은 모두
Application
와 관련된 생명주기와 관련된 방법입니다. 그런데 왜 우리가 계승한 것은 일반적인 종류가 아니라 진정한 ApplicationLike
입니다. 앱은 붕괴되지 않고 정상적으로 작동할 수 있다고 믿습니다. 똑똑한 당신은 이미 생각했을 것입니다. 그렇습니다.tinker가 채택한 방법은 바로 Application을 분리하고 에이전트 모델을 사용하여 우리 글에서 처음에 언급한 문제를 해결하는 것이다.그래서 여기서 주의해야 할 점은 this
에서 Application 대상을 사용해야 한다면 ApplicationLike
키워드를 사용할 수 없다는 것이다. 왜냐하면 이것은 진정한 의미의 Application이 아니라 getApplication
의 TinkerApplication
방법으로 응용 프로그램 대상을 얻어야 하기 때문이다.그렇다면 틴커 중 진정한 애플리케이션은 무엇일까?실제로는
TinkerApplication
입니다. 여기서 TinkerApplication
의 원본 코드를 먼저 보겠습니다.public abstract class TinkerApplication extends Application {
...
private ApplicationLike applicationLike = null;
/**
* current build.
*/
protected TinkerApplication(int tinkerFlags) {
this(tinkerFlags, "com.tencent.tinker.loader.app.DefaultApplicationLike", TinkerLoader.class.getName(), false);
}
/**
* @param delegateClassName The fully-qualified name of the {@link ApplicationLifeCycle} class
* that will act as the delegate for application lifecycle callbacks.
*/
protected TinkerApplication(int tinkerFlags, String delegateClassName,
String loaderClassName, boolean tinkerLoadVerifyFlag) {
this.tinkerFlags = tinkerFlags;
this.delegateClassName = delegateClassName;
this.loaderClassName = loaderClassName;
this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag;
}
protected TinkerApplication(int tinkerFlags, String delegateClassName) {
this(tinkerFlags, delegateClassName, TinkerLoader.class.getName(), false);
}
private ApplicationLike createDelegate() {
try {
// ApplicationLike
Class> delegateClass = Class.forName(delegateClassName, false, getClassLoader());
Constructor> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class,
long.class, long.class, Intent.class);
return (ApplicationLike) constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag,
applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
} catch (Throwable e) {
throw new TinkerRuntimeException("createDelegate failed", e);
}
}
private synchronized void ensureDelegate() {
if (applicationLike == null) {
applicationLike = createDelegate();
}
}
private void onBaseContextAttached(Context base) {
applicationStartElapsedTime = SystemClock.elapsedRealtime();
applicationStartMillisTime = System.currentTimeMillis();
// tinker patch
loadTinker();
// ApplicationLike
ensureDelegate();
// ApplicationLike
applicationLike.onBaseContextAttached(base);
...
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this));
onBaseContextAttached(base);
}
private void loadTinker() {
//disable tinker, not need to install
if (tinkerFlags == TINKER_DISABLE) {
return;
}
tinkerResultIntent = new Intent();
try {
// TinkLoader tryLoad
Class> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());
Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class, int.class, boolean.class);
Constructor> constructor = tinkerLoadClass.getConstructor();
tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this, tinkerFlags, tinkerLoadVerifyFlag);
} catch (Throwable e) {
//has exception, put exception error code
ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
}
}
@Override
public void onCreate() {
super.onCreate();
ensureDelegate();
applicationLike.onCreate();
}
@Override
public void onTerminate() {
super.onTerminate();
if (applicationLike != null) {
applicationLike.onTerminate();
}
}
@Override
public void onLowMemory() {
super.onLowMemory();
if (applicationLike != null) {
applicationLike.onLowMemory();
}
}
@TargetApi(14)
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (applicationLike != null) {
applicationLike.onTrimMemory(level);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (applicationLike != null) {
applicationLike.onConfigurationChanged(newConfig);
}
}
...
}
이를 통해 알 수 있듯이
createDelegate
는 Application에서 계승된 진정한 의미의 Application이다. 그 구조 방법에서 우리는 에이전트가 필요한 Application 유형을 전달한 다음에 ApplicationLike
방법에서 반사를 이용하여 ApplicationLike
의 대상을 구축하고 자신의 생명주기에 대응하는 방법을 사용했다.그래서 ApplicationLike
가 진정한 Application이 아니더라도 Application의 생명 주기를 가질 수 있다.또한 onBaseContextAttached
방법에서 loadTinker
방법은 ensureDelegate
방법보다 먼저 호출되었기 때문에 ApplicationLike
의 창설은 패치 합성 삽입 후에 발생할 수 있기 때문에 Application의 클래스도 열로 복원될 수 있는 목적을 달성할 수 있다.여기까지 우리는 전체 대리의 과정을 파악했고 또 하나의 문제는 우리가 계승
ApplicationLike
한 후의 자류가 어떻게 TinkerApplication
와 연관되었는가이다.여기에서 당신은 당연히 계승
TinkerApplication
의 방법을 사용하고 Application에서 조작해야 하는 모든 코드를 계승ApplicationLike
의 하위 클래스에 넣고 클래스 이름을 TinkerApplication
구조 방법을 통해 전달할 수 있습니다. 그러나 이렇게 하면 나중에 코드를 유지하는 사람에게 어느 정도 오해를 하기 쉽습니다. Tinker는 개발자의 편의를 위해에이전트를 최대한 차단하기 위해 주석을 컴파일하는 과정을 제공했고 주석을 사용하여 Application 클래스를 생성하는 것도 팅커 공식 문서에서 추천하는 방식이다.사용하는 방식도 간단합니다. 당신의
ApplicationLike
에 다음과 같은 주석을 추가하세요.@DefaultLifeCycle(
application = ".SampleApplication", //application
flags = ShareConstants.TINKER_ENABLE_ALL, //tinkerFlags
loaderClass = "com.tencent.tinker.loader.TinkerLoader", //loaderClassName, !
loadVerifyFlag = false) //tinkerLoadVerifyFlag
public class YourApplicationLike extends DefaultApplicationLike {
...
}
여기서 우리도 주해 논리를 처리하는
AnnotationProcessor
류를 살펴보았는데, 그 중에서processDefaultLifeCycle
가 바로 이 주해를 처리하는 방법이다@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotationProcessor extends AbstractProcessor {
private static final String APPLICATION_TEMPLATE_PATH = "/TinkerAnnoApplication.tmpl";
...
private void processDefaultLifeCycle(Set extends Element> elements) {
// DefaultLifeCycle
for (Element e : elements) {
//
DefaultLifeCycle ca = e.getAnnotation(DefaultLifeCycle.class);
//
String lifeCycleClassName = ((TypeElement) e).getQualifiedName().toString();
//
String lifeCyclePackageName = lifeCycleClassName.substring(0, lifeCycleClassName.lastIndexOf('.'));
//
lifeCycleClassName = lifeCycleClassName.substring(lifeCycleClassName.lastIndexOf('.') + 1);
// Application
String applicationClassName = ca.application();
//
if (applicationClassName.startsWith(".")) {
applicationClassName = lifeCyclePackageName + applicationClassName;
}
String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.'));
applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1);
// loaderClass
String loaderClassName = ca.loaderClass();
// loaderClass
if (loaderClassName.startsWith(".")) {
loaderClassName = lifeCyclePackageName + loaderClassName;
}
System.out.println("*");
//
final InputStream is = AnnotationProcessor.class.getResourceAsStream(APPLICATION_TEMPLATE_PATH);
final Scanner scanner = new Scanner(is);
final String template = scanner.useDelimiter("\\A").next();
//
final String fileContent = template
.replaceAll("%PACKAGE%", applicationPackageName)
.replaceAll("%APPLICATION%", applicationClassName)
.replaceAll("%APPLICATION_LIFE_CYCLE%", lifeCyclePackageName + "." + lifeCycleClassName)
.replaceAll("%TINKER_FLAGS%", "" + ca.flags())
.replaceAll("%TINKER_LOADER_CLASS%", "" + loaderClassName)
.replaceAll("%TINKER_LOAD_VERIFY_FLAG%", "" + ca.loadVerifyFlag());
try {
// java
JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(applicationPackageName + "." + applicationClassName);
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating " + fileObject.toUri());
Writer writer = fileObject.openWriter();
try {
PrintWriter pw = new PrintWriter(writer);
pw.print(fileContent);
pw.flush();
} finally {
writer.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, x.toString());
}
}
}
}
복잡하지 않다. 주요 과정은 템플릿 파일을 읽고 대응하는 문자열을 교체한 다음에 자바 파일로 쓰는 것이다. 템플릿 파일은 사실
TinkerAnnoApplication.tmpl
이다. 내용은 다음과 같다.package %PACKAGE%;
import com.tencent.tinker.loader.app.TinkerApplication;
/**
*
* Generated application for tinker life cycle
*
*/
public class %APPLICATION% extends TinkerApplication {
public %APPLICATION%() {
super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%);
}
}
이로써 애플리케이션에 대한 전체 에이전트를 완성했고 이를 실현하는 방식이 교묘하여 팅커도 애플리케이션에 사용된 클래스를 열 복구할 수 있게 되었다.
총결산
원본 코드를 읽으면 우리는 전체 구조에 대한 이해와 원리를 더욱 분명하게 할 수 있다. 여기서도 여러분들이 시간이 있으면 우수한 원본 코드를 많이 보는 것을 추천합니다. 이것은 전체 프로그래밍 사상에 확실히 도움이 됩니다.그러면 본문은 여기서 일단락을 짓도록 하겠습니다. 만약에 오류가 있거나 상세하지 않은 부분이 있으면 댓글로 남겨주시기 바랍니다. 다음에 Tinker 원본에서 patch에 관한 부분을 보여드릴 테니 기대해 주세요. 감사합니다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.