Android 동적 교체 애플 리 케 이 션 구현

8212 단어 AndroidApplication
배경
핫 업데이트 와 훅 기술 이 모두 엉망 이 되 었 지만 이 방면 의 내용 을 여러분 과 이야기 하고 싶 습 니 다.최근 에 안 드 로 이 드 분야 의 최적화 작업 을 하고 있 습 니 다.안 드 로 이 드 의 ClassLoader 는 dex 파일 을 불 러 오 는 과정 에서 안 드 로 이 드 Manifest 의 Application 류 는 dex 파일 에 있 습 니 다.Application 은 보통 전체적인 초기 화 작업 을 합 니 다.dex 를 불 러 오기 전에 기 존의 Application 을 ProxyApplication 으로 교체 해 야 합 니 다.응용 프로그램 을 시작 할 때 ProxyApplication 을 불 러 온 다음 dex 를 불 러 오 는 등 일부 프로 세 스 를 처리 합 니 다.그 다음 에 기 존의 Application(이하 RealApplication)을 교체 하여 응용 이 정상적으로 작 동 하도록 확보 하고 생명 주기,초기 화 순서 가 변 하지 않 으 며 응용 중의 getContext,getapplicationContext 에 미 치 는 영향 을 차단 해 야 합 니 다.
응용 프로그램 을 교체 하 는 과정 에서 다음 과 같은 몇 가 지 를 주의해 야 한다.
  • RealApplication 을 만 들 고 정상 적 인 생명 주 기 를 유지 하 며 리 셋 을 한다
  • 4.567917.응용 프로그램 에서 프 록 시 애플 리 케 이 션 을 차단 하고 하층부 에 대해 감지 하지 않 습 니 다.Activity 등 이 getApplication Context 를 호출 한 후에 RealApplication 으로 돌아 가 야 합 니 다
  • ContentProvider 생 성 시기 가 비교적 특수 하 므 로 정상 적 인 초기 화 순 서 를 만족 시 킨 후에 도 ProxyApplication 의 존 재 를 차단 해 야 합 니 다
  • 방안 실현
    AndroidManifest.xml 파일 에서 Application 을 ProxyApplication 으로 바 꾸 면 자동화 방식 이나 포장 방식 을 사용 할 수 있 으 며 실현 에 대한 구체 적 인 세부 사항 은 논의 하지 않 습 니 다.여 기 는 주로 RealApplication 을 만 드 는 과정 을 서술 합 니 다.프 록 시 애플 리 케 이 션 을 교체 한 후에 시스템 에 있어 프 록 시 애플 리 케 이 션 은 초기 화 된 입구 이 고 모든 리 셋 은 프 록 시 애플 리 케 이 션 에서 발생 합 니 다.우 리 는 주로 attachBaseContext 와 onCreate 의 리 셋 에 관심 을 가진다.
    RealApplication 만 들 기
    RealApplication 을 만 듭 니 다.저 희 는 반사 적 인 방식 으로 new Instance 로 대상 을 만 든 다음 에 attachBaseContext 를 실행 할 수 있 습 니 다.그러나 서로 다른 시스템 버 전에 대해 내부 에서 실 행 된 세부 사항 이 다 르 거나 다른 관련 논리 적 인 처리 가 있 을 수 있 기 때문에 우 리 는 다른 방식 으로 처리 합 니 다.먼저 시스템 소스 코드 가 어떻게 실현 되 는 지 살 펴 보고 여 기 는 8.0.0 의 시스템 소스 코드 를 선택 하여 분석 하고 다른 버 전 은http://androidxref.com/조회 합 니 다.
    안 드 로 이 드 초기 화 는 안 드 로 이 드.app.activity Thread 에서 시작 되 었 다 는 것 을 알 고 있 습 니 다.그래서 Activity Thread 부터 보 세 요.Activity Thread 에 정적 방법 이 존재 합 니 다.current Activity Thread 는 인 스 턴 스 를 되 돌려 줍 니 다.시스템 의 Activity Thread 클래스 를 참고 할 수 있 습 니 다.
    
    public static ActivityThread currentActivityThread() {
      return sCurrentActivityThread;
     }
    Activity Thread 내부 에 구성원 변수 AppBindData mBoundApplication 이 존재 합 니 다.AppBindData 는 구성원 변수 LoadedApk info 를 포함 하 는 정적 내부 클래스 입 니 다.android.app.LoadedApk 소스 코드 를 보고 응용 프로그램 을 만 드 는 makeApplication 방법 을 발견 합 니 다.
    
     public Application makeApplication(boolean forceDefaultAppClass,
       Instrumentation instrumentation) {
      if (mApplication != null) {
       return mApplication;
      }
    
      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
    
      Application app = null;
    
      String appClass = mApplicationInfo.className;
      if (forceDefaultAppClass || (appClass == null)) {
       appClass = "android.app.Application";
      }
    
      try {
       java.lang.ClassLoader cl = getClassLoader();
       if (!mPackageName.equals("android")) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
          "initializeJavaContextClassLoader");
        initializeJavaContextClassLoader();
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
       }
       ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
       app = mActivityThread.mInstrumentation.newApplication(
         cl, appClass, appContext);
       appContext.setOuterContext(app);
      } catch (Exception e) {
       if (!mActivityThread.mInstrumentation.onException(app, e)) {
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        throw new RuntimeException(
         "Unable to instantiate application " + appClass
         + ": " + e.toString(), e);
       }
      }
      mActivityThread.mAllApplications.add(app);
      mApplication = app;
    
      if (instrumentation != null) {
       try {
        instrumentation.callApplicationOnCreate(app);
       } catch (Exception e) {
        if (!instrumentation.onException(app, e)) {
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         throw new RuntimeException(
          "Unable to create application " + app.getClass().getName()
          + ": " + e.toString(), e);
        }
       }
      }
    
      // Rewrite the R 'constants' for all library apks.
      SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
        .getAssignedPackageIdentifiers();
      final int N = packageIdentifiers.size();
      for (int i = 0; i < N; i++) {
       final int id = packageIdentifiers.keyAt(i);
       if (id == 0x01 || id == 0x7f) {
        continue;
       }
    
       rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
      }
    
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    
      return app;
     }
    위의 코드 를 통 해 캐 시 mApplication 이 비어 있 지 않 으 면 바로 돌아 오 는 것 을 발견 할 수 있 습 니 다.mApplication 이 비어 있 을 때 RealApplication 을 만 들 고 관련 리 셋 을 실행 합 니 다.RealApplication 을 만 들 때 클래스 이름 은 mApplication Info.className 에서 가 져 옵 니 다.
    mActivity Thread.malApplications 에 RealApplication 을 새로 만 듭 니 다.캐 시 mApplication 에 값 을 부여 합 니 다.따라서 MakeApplication 을 호출 하기 전에 mApplication 을 null 로 설정 해 야 합 니 다.그렇지 않 으 면 ProxyApplication 의 인 스 턴 스 를 직접 되 돌려 줍 니 다.
    우선 android.app.activity Thread 의 정적 방법 으로 Activity Thread 인 스 턴 스 를 얻 은 다음 Activity Thread 인 스 턴 스 를 통 해 LoadedApk 인 스 턴 스 를 얻 습 니 다.MakeApplication 이 순조롭게 실 행 될 수 있 도록 mApplication 을 null 로 설정 합 니 다.mAllApplications 에서 ProxyApplication 의 인 스 턴 스 를 제거 합 니 다.LoadedApk 에서 mApplicationInfo 와 AppBindData 에서 appInfo 는 모두 ApplicationInfo 형식 으로 className 필드 의 값 을 RealApplication 의 실제 클래스 전체 이름 으로 각각 교체 해 야 합 니 다.
     
    이후 반사 호출 시스템 의 MakeApplication.

    이렇게 하면 Proxyapplication.attachBaseContext 에서 MakeApplication 을 호출 하여 RealApplication 을 만 들 고 내부 적 으로 RealApplication 에 대한 attchBaseContext 의 리 셋 이 완료 되 었 습 니 다.Proxyapplication.onCreate 에서 RealApplication 인 스 턴 스 의 onCreate 만 바 꾸 면 RealApplication 생 성,내부 교체 및 정상 적 인 수명 주기 에 대한 리 셋 을 완성 할 수 있 습 니 다.또한 Activity 에서 getApplication Context 를 호출 하여 되 돌려 주 는 값 은 실제 LoadedApk 에서 mApplication 의 값 이자 Activity 등 부분 에서 ProxyApplication 을 차단 하 는 목적 도 보장 합 니 다.
    ContentProvider 에서 getContext
    응용 프로그램 과 ContentProvider 의 초기 화 순 서 는:응용 프로그램.attachBaseContext->ContentProvider.onCreate->응용 프로그램.onCreate 입 니 다.ContentProvider 에 도 getContext 방법 이 존재 합 니 다.ContentProvider 의 소스 코드 구현 을 보십시오.

    그 중에서 mContext 가 할당 되 는 곳 은 두 군데 가 있 는데 하 나 는 구조 방법 이 고 하 나 는 attchInfo 일 때 입 니 다.원본 코드 에서 구조 방법 을 사용 하여 초기 화 하거나 attachInfo 를 호출 한 곳 을 계속 추적 한 결과 android.app.activity Thread 에서 installProvider 방법 을 찾 으 면 호출 관계 가 존재 합 니 다.
     
    이 를 통 해 알 수 있 듯 이 반사 호출 ContentProvider 무 참 구조 방법 으로 인 스 턴 스 를 만 든 다음 에 attachInfo 를 호출 하여 전달 하 는 Context 는 installProvider 방법 중의 매개 변수 이 고 installProvider 의 매개 변 수 는 installContentProviders 내부 에서 초기 화 에서 전 달 된 것 입 니 다.

    installContentProviders 에서 installProvider 를 호출 할 때 전달 하 는 Context 도 방법 이 호출 될 때 전달 하 는 매개 변수 임 을 명확 하 게 할 수 있다.계속 위로 추적 해 보 니 Activity Thread.handleBindApplication 이 ContentProvider 를 초기 화 할 때 installContentProviders 를 호출 했 습 니 다.마지막 으로 attach Info 를 통 해 ContentProvider 에 설 치 된 Context 의 실제 유형 은 Application 입 니 다.
    앱 이 초기 화 되 었 을 때 시스템 이 MakeApplication 을 호출 하여 ProxyApplication 인 스 턴 스 를 만 들 었 고 attachBaseContext(Context context)를 되 돌 렸 습 니 다.그래서 이 방법 은 앱 초기 화 시 프 록 시 애플 리 케 이 션 을 되 돌려 줍 니 다.프 록 시 애플 리 케 이 션.attachBaseContext 가 발생 한 후 프 록 시 애플 리 케 이 션.onCreate 이전 을 호출 합 니 다.그래서 우 리 는 이 두 가지 방법 으로 수명 주기 내 에 RealApplication 으로 바 꿀 방법 이 없다.
    이런 방안 은 접속 원가 가 비교적 낮 지만 새로운 시스템 이 발생 한 후에 호환성 문제 가 발생 할 수 있 으 므 로 매번 새로운 시스템 을 발표 한 후에 관련 된 적절 한 배 치 를 해 야 한다.하지만 이런 훅 이 문 제 를 해결 하 는 방향 은 참고 할 수 있다.
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기