Android App 개발 에서 Context 의 용법 을 깊이 분석 하 다.

14243 단어 AndroidContext
Context 는 안 드 로 이 드 애플 리 케 이 션 을 개발 하 는 과정 에서 매우 중요 한 역할 을 합 니 다.예 를 들 어 Activity 를 시작 하려 면 context.startActivity 방법 을 사용 해 야 합 니 다.xml 파일 을 View 대상 으로 전환 하 는 데 도 Context 대상 을 사용 해 야 합 니 다.이렇게 말 하면 안 드 로 이 드 개발 은 한 걸음 도 움 직 이지 못 합 니 다.이런 유형 에 대해 우 리 는 그 에 대해 얼마나 알 고 있 습 니까?제 소감 을 말씀 드 리 겠 습 니 다.안 드 로 이 드 개발 을 처음 배 웠 을 때 Context 를 사용 하 는 곳 은 항상 하나의 Activity 대상 이 들 어 온 것 같 습 니 다.오 랜 시간 동안 Context 가 있 는 곳 이 라면 하나의 Activity 로 들 어 오 면 된다 는 느낌 이 들 었 습 니 다.그러면 우 리 는 지금 Context 와 Activity 의 관 계 를 상세 하 게 분석 해 보 겠 습 니 다!
본문 을 시작 하기 전에 우 리 는 먼저 문 제 를 여기에 두 었 다.
우 리 는 평소에 프로젝트 자원 을 가 져 올 때 context.getResources()를 사용 할 때 왜 같은 값 을 넣 었 습 니까?서로 다른 Activity 를 사용 하여 getResources 를 호출 한 결 과 는 똑 같 습 니 다.
2016227165415276.jpg (541×283)
Context 자 체 는 순수한 abstract 류 입 니 다.ContextWrapper 는 Context 에 대한 포장 일 뿐 입 니 다.그 내부 에 Context 대상 이 포함 되 어 있 습 니 다.사실은 ContextWrapper 의 방법 에 대한 호출 은 최종 적 으로 그 중의 Context 대상 을 호출 하여 이 루어 졌 습 니 다.ContextThremeWrapper 는 Theme 와 관련 이 있 기 때문에 Activity 는 ContextThemmWrapper 에서 계승 되 었 습 니 다.한편,Service 는 ContextWrapper 에서 계승 되 었 고 ContextImpl 은 Context 에서 방법 을 진정 으로 실현 한 유일한 유형 이다.
 
위의 상속 관 계 를 보면 모든 Activity 는 하나의 Context 이 고 모든 Service 는 하나의 Context 이다.이것 이 바로 Context 를 사용 하 는 곳 이 Activity 나 Service 로 바 뀔 수 있 는 이유 이다.
 
Context 만 들 기
앞에서 말 한 바 와 같이 Context 를 실현 한 것 은 ContextImpl 류 만 있 기 때문에 Activity 와 Service 는 실제 실현 되 지 않 았 습 니 다.그들 은 내부 에 진실 한 Context 대상 을 포함 하고 있 을 뿐 입 니 다.즉,Activity 나 Service 를 만 들 때 반드시 ContextImpl 대상 을 만 들 고 Activity 의 Context 유형 변수 에 값 을 부여 해 야 합 니 다.그러면 Andorid 소스 코드 중 어떤 곳 이 ContextImpl 을 만 들 었 는 지 살 펴 보 겠 습 니 다.
통계 에 따 르 면 Android 에서 ContextImpl 을 만 든 곳 은 모두 7 곳 입 니 다.
  • Package Info.makeApplication()에서
  • perform LaunchActivity()에서
  • handle Create BackupAgent()에서handle Create Service()에서
  • 2 회 hanldBinder Appplication()에서
  • attach()방법 에서
  • ContextImpl 을 만 드 는 기본 원리 가 유사 하기 때문에 대표 적 인 부분 만 분석 할 수 있 습 니 다.
    1、  애플 리 케 이 션 에 대응 하 는 Context
    프로그램 이 시 작 될 때 애플 리 케 이 션 대상 을 만 들 기 때문에 handle BindApplication()방법 으로 전전 합 니 다.
    
    private final void handleBindApplication(AppBindData data) { 
        mBoundApplication = data; 
        mConfiguration = new Configuration(data.config); 
     
        .... 
        data.info = getPackageInfoNoCheck(data.appInfo); 
     
          ... 
         
        Application app = data.info.makeApplication(data.restrictedBackupMode, null); 
        mInitialApplication = app; 
     
       .... 
     } 
    
    
    그 중에서 data.info 는 LoadedApk 형식 입 니 다.getPackage InfoNo Check 에서 원본 코드 를 보 세 요.
    
    public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) { 
        return getPackageInfo(ai, null, false, true); 
    } 
    
    
    안에 있 는 것 은 getPackage Info 입 니 다.계속 따라 갑 니 다.
    
    if (includeCode) { 
            ref = mPackages.get(aInfo.packageName); 
          } else { 
            ref = mResourcePackages.get(aInfo.packageName); 
          } 
          LoadedApk packageInfo = ref != null ? ref.get() : null; 
          if (packageInfo == null || (packageInfo.mResources != null 
              && !packageInfo.mResources.getAssets().isUpToDate())) { 
            if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " 
                : "Loading resource-only package ") + aInfo.packageName 
                + " (in " + (mBoundApplication != null 
                    ? mBoundApplication.processName : null) 
                + ")"); 
            packageInfo = 
              new LoadedApk(this, aInfo, this, baseLoader, 
                  securityViolation, includeCode && 
                  (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); 
    if (includeCode) { 
              mPackages.put(aInfo.packageName, 
                  new WeakReference<LoadedApk>(packageInfo)); 
            } else { 
              mResourcePackages.put(aInfo.packageName, 
                  new WeakReference<LoadedApk>(packageInfo)); 
            } 
    
    include Code 가 true 로 들 어 오기 때문에 먼저 mPackages 에서 가 져 옵 니 다.없 으 면 new 가 나 오고 mPackages 에 넣 습 니 다.여기 mPackages 는 Activity Thread 의 속성 입 니 다.
    로드 APK 클래스 의 MakeApplication 함 수 를 분석 해 보 겠 습 니 다.
    
    try { 
          java.lang.ClassLoader cl = getClassLoader(); 
          //    ContextImpl   
          ContextImpl appContext = new ContextImpl(); 
          appContext.init(this, null, mActivityThread); 
          app = mActivityThread.mInstrumentation.newApplication( 
              cl, appClass, appContext); 
          appContext.setOuterContext(app); 
        } catch (Exception e) { 
          if (!mActivityThread.mInstrumentation.onException(app, e)) { 
            throw new RuntimeException( 
              "Unable to instantiate application " + appClass 
              + ": " + e.toString(), e); 
          } 
        } 
    
    ContextImpl 대상 을 만 들 고 init 방법 을 호출 했 습 니 다.현재 init 방법 으로 들 어 갑 니 다. 
    
    mPackageInfo = packageInfo; 
    mResources = mPackageInfo.getResources(mainThread); 
    
    mPackage Inof 와 mResources 두 변 수 를 초기 화 합 니 다.
    MakeApplication 으로 돌아 가 애플 리 케 이 션 대상 을 만 들 고 appContext 를 전송 합 니 다.사실은 appContext 를 ContextWrapper 에 전달 하 는 Context 형식 변수(Application 도 ContextWrapper 계승)입 니 다.
    2.Activity 의 Context
    Activity 를 만 들 때 전전 호출 을 통 해 handle LaunchActivity()를 실행 한 다음 permLaunchActivity()를 호출 합 니 다.이 방법 은 ContextImpl 코드 를 다음 과 같이 만 듭 니 다.
    
    r.packageInfo= getPackageInfo(aInfo.applicationInfo,
              Context.CONTEXT_INCLUDE_CODE);
     
    ContextImplappContext = new ContextImpl();
            appContext.init(r.packageInfo,r.token, this);
            appContext.setOuterContext(activity);
     
    activity.attach(appContext,this, getInstrumentation(), r.token,
                r.ident, app, r.intent,r.activityInfo, title, r.parent,
                r.embeddedID,r.lastNonConfigurationInstance,
                r.lastNonConfigurationChildInstances, config);
    
    getPackageInfo 함수 가 이전에 분석 되 었 기 때문에 약간의 차이 가 있 지만 대체적으로 절차 가 차이 가 많 지 않 기 때문에 이곳 의 appContext 가 init 를 실행 한 후에 그 중의 mPackages 변 수 는 mResources 변수 와 같 습 니 다.activity 는 attach 함 수 를 통 해 이 appContext 를 ContextWrapper 의 Context 형식 변수 에 할당 합 니 다.
    3.서비스 중의 Context
    마찬가지 로 Service 를 만 들 때,전전 호출 을 통 해 scheduleCreateService 방법 으로 호출 되 며,그 후에 handle CreateService 를 교묘 하 게 사용 합 니 다.
    
    LoadedApkpackageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo);
     
    ContextImplcontext = new ContextImpl();
          context.init(packageInfo, null,this);
     
          Application app =packageInfo.makeApplication(false, mInstrumentation);
          context.setOuterContext(service);
          service.attach(context, this,data.info.name, data.token, app,
              ActivityManagerNative.getDefault());
    
    그 사고방식 은 위의 두 가지 기본 과 마찬가지 로 여기 서 더 이상 상술 하지 않 는 다.
    Context 자원 접근
    서로 다른 Context 가 얻 은 것 은 모두 같은 자원 이라는 것 이 명확 하 다.이것 은 이해 하기 쉬 운 것 입 니 다.아래 의 분석 을 보 세 요.
    자원 을 얻 는 방식 은 context.getResources 입 니 다.ContextImpl 에 있 는 getResources 방법 을 진정 으로 실현 합 니 다.ContextImpl 에 있 는 한 구성원 private Resources mResources 가 있 습 니 다.이것 은 getResources 방법 으로 돌아 온 결과 입 니 다.mResources 의 할당 코드 는 다음 과 같 습 니 다.
    
    mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
              Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
    
    
    리 소스 관리자 의 getTopLeveleResources 방법 을 살 펴 보 겠 습 니 다.이 방법 은 다음 과 같 습 니 다.리 소스 관리자 에 서 는 모든 자원 대상 이 Array Map 에 저 장 됩 니 다.먼저 현재 요청 매개 변수 에 따라 자원 을 찾 고 찾 으 면 되 돌아 갑 니 다.그렇지 않 으 면 자원 대상 을 만들어 Array Map 에 넣 습 니 다.한 가지 설명 할 것 은 왜 여러 개의 자원 대상 이 있 는 지 하 는 것 입 니 다.이 유 는 간단 합 니 다.res 에 서로 다른 장치,서로 다른 해상도,서로 다른 시스템 버 전의 디 렉 터 리 가 존재 할 수 있 기 때 문 입 니 다.안 드 로 이 드 시스템 의 디자인 에 따라 서로 다른 장치 가 같은 응용 프로그램 을 방문 할 때 방문 하 는 자원 이 다 를 수 있 습 니 다.예 를 들 어 drawable-hdpi 와 drawable-xhdpi 가 전형 적 인 예 입 니 다.
    
    public Resources getTopLevelResources(String resDir, int displayId, 
        Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { 
      final float scale = compatInfo.applicationScale; 
      ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, 
          token); 
      Resources r; 
      synchronized (this) { 
        // Resources is app scale dependent. 
        if (false) { 
          Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale); 
        } 
        WeakReference<Resources> wr = mActiveResources.get(key); 
        r = wr != null ? wr.get() : null; 
        //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); 
        if (r != null && r.getAssets().isUpToDate()) { 
          if (false) { 
            Slog.w(TAG, "Returning cached resources " + r + " " + resDir 
                + ": appScale=" + r.getCompatibilityInfo().applicationScale); 
          } 
          return r; 
        } 
      } 
     
      //if (r != null) { 
      //  Slog.w(TAG, "Throwing away out-of-date resources!!!! " 
      //      + r + " " + resDir); 
      //} 
     
      AssetManager assets = new AssetManager(); 
      if (assets.addAssetPath(resDir) == 0) { 
        return null; 
      } 
     
      //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); 
      DisplayMetrics dm = getDisplayMetricsLocked(displayId); 
      Configuration config; 
      boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 
      final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 
      if (!isDefaultDisplay || hasOverrideConfig) { 
        config = new Configuration(getConfiguration()); 
        if (!isDefaultDisplay) { 
          applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config); 
        } 
        if (hasOverrideConfig) { 
          config.updateFrom(key.mOverrideConfiguration); 
        } 
      } else { 
        config = getConfiguration(); 
      } 
      r = new Resources(assets, dm, config, compatInfo, token); 
      if (false) { 
        Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " 
            + r.getConfiguration() + " appScale=" 
            + r.getCompatibilityInfo().applicationScale); 
      } 
     
      synchronized (this) { 
        WeakReference<Resources> wr = mActiveResources.get(key); 
        Resources existing = wr != null ? wr.get() : null; 
        if (existing != null && existing.getAssets().isUpToDate()) { 
          // Someone else already created the resources while we were 
          // unlocked; go ahead and use theirs. 
          r.getAssets().close(); 
          return existing; 
        } 
     
        // XXX need to remove entries when weak references go away 
        mActiveResources.put(key, new WeakReference<Resources>(r)); 
        return r; 
      } 
    } 
    
    상기 코드 에서 자원 의 요청 체제 에 따라 리 소스 관리자 가 단일 모드 를 사용 하면 서로 다른 ContextImpl 이 같은 자원 에 접근 할 수 있 습 니 다.여기 서 말 하 는 같은 자원 이 반드시 같은 자원 이 아니 라 자원 이 서로 다른 디 렉 터 리 에 있 을 수 있 지만 이것 은 반드시 우리 의 응용 자원 일 것 입 니 다.이렇게 설명 하 는 것 이 더욱 정확 할 수도 있 습 니 다.장치 매개 변수 와 디 스 플레이 매개 변수 가 변 하지 않 는 상황 에서 서로 다른 ContextImpl 은 같은 자원 에 접근 합 니 다.장치 매개 변수 가 변 하지 않 는 것 은 핸드폰 의 화면 과 안 드 로 이 드 버 전이 변 하지 않 는 것 을 말 하 는데 디 스 플레이 매개 변수 가 변 하지 않 는 것 은 핸드폰 의 해상도 와 가로 세로 화면 상 태 를 말한다.즉,application,Activity,Service 는 모두 자신의 ContextImpl 이 있 고 모든 ContextImpl 은 자신의 mResources 구성원 이 있 지만 그들의 mResources 구성원 은 유일한 Resource Manager 인 스 턴 스 에서 왔 기 때문에 서로 다른 mResources 는 같은 메모리(C 언어의 개념)를 가리 키 기 때문에그들의 mResources 는 모두 같은 대상 이다.가로 세로 화면 이 전환 되 는 상황 에서 응용 에서 가로 화면 상태 에 서로 다른 자원 을 제공 합 니 다.가로 화면 상태 에 있 는 ContextImpl 과 세로 화면 상태 에 있 는 ContextImpl 이 방문 하 는 자원 은 같은 자원 대상 이 아 닙 니 다.
     
    코드:단일 모드 의 리 소스 관리자 클래스
    
    public static ResourcesManager getInstance() { 
      synchronized (ResourcesManager.class) { 
        if (sResourcesManager == null) { 
          sResourcesManager = new ResourcesManager(); 
        } 
        return sResourcesManager; 
      } 
    } 
    
    getapplication 과 getapplicationContext 의 차이
     
    getapplication 반환 결 과 는 application 이 며,서로 다른 Activity 와 Service 가 돌아 오 는 application 은 모두 같은 전역 대상 입 니 다.Activity Thread 내부 에 모든 애플 리 케 이 션 을 유지 하 는 데 사용 되 는 목록 이 있 습 니 다.
     
    
      final ArrayList<Application> mAllApplications = new ArrayList<Application>();
    
    getapplicationContext 가 되 돌아 오 는 것 도 Application 대상 입 니 다.다만 되 돌아 오 는 유형 은 Context 입 니 다.실현 을 보 세 요.
    
    @Override 
    public Context getApplicationContext() { 
      return (mPackageInfo != null) ? 
          mPackageInfo.getApplication() : mMainThread.getApplication(); 
    } 
    
    상기 코드 에서 mPackageInfo 는 현재 응용 되 고 있 는 패키지 정보,예 를 들 어 패키지 이름,응용 되 는 설치 디 렉 터 리 등 을 포함 하 는 것 으로 원칙적으로 제3자 응용 으로서 패키지 정보 mPackageInfo 가 비어 있 을 수 없다.이런 상황 에서 getApplication Context 가 돌아 오 는 대상 과 getApplication 은 같다.그러나 시스템 응용 에 있어 가방 정보 가 비어 있 을 수 있 고 구체 적 으로 깊이 연구 하지 않 는 다.이러한 측면 에서 볼 때 제3자 응용 에 있어 하나의 응용 프로그램 은 하나의 응용 프로그램 대상 만 존재 하고 getApplication 과 getapplicationContext 를 통 해 얻 은 것 은 같은 대상 이 며 이들 의 차 이 는 반환 유형 만 다르다.
    여기 서 요약 하면:
    (1)Context 는 추상 적 인 클래스 입 니 다.ContextWrapper 는 Context 에 대한 패키지 입 니 다.Context 유형의 변 수 를 포함 하고 ContextWrapper 의 기능 함수 내 부 는 모두 안에 있 는 Context 형식 변 수 를 호출 하여 완 성 된 것 입 니 다.application,Service,Activity 등 은 모두 Context Wrapper 에서 직접 또는 간접 적 으로 계승 되 었 으 나 그 중의 기능 을 진정 으로 실현 하지 못 했다.Application,Service,Activity 에서 Context 에 관 한 기능 은 모두 내부 의 Context 유형 변 수 를 통 해 이 루어 졌 고 이 변수의 실제 대상 은 반드시 ContextImpl 이기 때문에 application,Activity 를 만 들 지 않 았 다.Servcice 는 ContextImpl 을 만 들 고 이 ContextImpl 의 mPackages 와 mResources 변 수 는 같 기 때문에 Acitivty 를 사용 하 든 Service 를 사용 하 든 getResources 를 호출 하 든 같은 결 과 를 얻 을 수 있 습 니 다.
    (2)한 apk 에서 Context 의 수량 은 Activity 개수+Service 개수+1 과 같 습 니 다.

    좋은 웹페이지 즐겨찾기