Tinker 소스 분석 제품군 (1) - Application 프록시 메커니즘

13877 단어
지난번에는 열복구의 원리를 분석해 실천에 옮겼고, 이번에는 팅커 원본을 탐구해 보도록 하겠습니다.
인용문
지난번 안드로이드 열복원 원리의 탐구와 실천에서 알 수 있듯이 팅커가 열복원을 실현하는 원리는 자신의 전량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이 아니라 getApplicationTinkerApplication 방법으로 응용 프로그램 대상을 얻어야 하기 때문이다.
그렇다면 틴커 중 진정한 애플리케이션은 무엇일까?실제로는 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에 관한 부분을 보여드릴 테니 기대해 주세요. 감사합니다.

좋은 웹페이지 즐겨찾기