Android App 개발 에서 Context 의 용법 을 깊이 분석 하 다.
본문 을 시작 하기 전에 우 리 는 먼저 문 제 를 여기에 두 었 다.
우 리 는 평소에 프로젝트 자원 을 가 져 올 때 context.getResources()를 사용 할 때 왜 같은 값 을 넣 었 습 니까?서로 다른 Activity 를 사용 하여 getResources 를 호출 한 결 과 는 똑 같 습 니 다.
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 곳 입 니 다.
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 과 같 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.