Android 5.1 WebView 메모리 누 출 문제 및 빠 른 해결 방법
프로젝트 의 메모리 누 출 을 조사 하 는 과정 에서 WebView 로 인 한 메모리 누 출 이 발견 되 었 으 며,테스트 를 통 해 이 부분의 누 출 은 안 드 로 이 드 5.1 이상 의 기종 에 만 나타 나 는 것 으로 밝 혀 졌 다.비록 프로젝트 가 WebView 를 사용 하 는 장면 은 많 지 않 지만 누설 도 놓 치지 않 는 정신 을 계승 하여 우 리 는 반드시 그것 을 해결 해 야 한다.
닥 친 문제
프로젝트 에서 WebView 를 사용 하 는 페이지 는 주로 FAQ 페이지 에 있 으 며,문제 도 여러 번 종료 에 들 어 갔 을 때 메모리 사용량 이 크 고 GC 가 빈번 한 것 으로 나 타 났 다.LeakCanary 를 사용 하여 관찰 한 결과 두 개의 메모리 누 출 이 빈번 한 것 을 발견 하 였 습 니 다.
우 리 는 이 두 가지 누설 을 분석 해 보 자.
그림 1 에서 웹 뷰 의 ContentView Core 의 구성원 변수 인 mContainer View 가 Accessibility Manager 의 mAccessibility State Change Listeners 를 인용 하여 activity 가 회수 되 지 못 해 유출 된 것 을 알 수 있다.
인용 관계:mAccessibilityStateChangeListeners->ContentViewCore->WebView->SettingHelpActivity
그림 2 에서 인용 관 계 는 mComponentCallbacks->AwContents->WebView->Setting HelpActivity 임 을 알 수 있다.
문제 분석
mAccessibility State Change Listeners 와 mComponentCallbacks 가 언제 등록 되 었 는 지 찾 아 보 겠 습 니 다.mAccessibility State Change Listeners 부터 살 펴 보 겠 습 니 다.
AccessibilityManager.java
private final CopyOnWriteArrayList<AccessibilityStateChangeListener>
mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<>();
/**
* Registers an {@link AccessibilityStateChangeListener} for changes in
* the global accessibility state of the system.
*
* @param listener The listener.
* @return True if successfully registered.
*/
public boolean addAccessibilityStateChangeListener(
@NonNull AccessibilityStateChangeListener listener) {
// Final CopyOnWriteArrayList - no lock needed.
return mAccessibilityStateChangeListeners.add(listener);
}
/**
* Unregisters an {@link AccessibilityStateChangeListener}.
*
* @param listener The listener.
* @return True if successfully unregistered.
*/
public boolean removeAccessibilityStateChangeListener(
@NonNull AccessibilityStateChangeListener listener) {
// Final CopyOnWriteArrayList - no lock needed.
return mAccessibilityStateChangeListeners.remove(listener);
}
위의 몇 가지 방법 은 Accessibility Manager.class 에서 정 의 된 것 입 니 다.방법 에 따라 호출 하면 ViewRootImpl 초기 화 에서 addAccessibility State Change Listener 를 호출 하여 listener 를 추가 한 다음 dispatchDetached FromWindow 에서 이 listener 를 제거 합 니 다.remove 가 있 는 이상 왜 계속 인용 하고 있 습 니까?우 리 는 잠시 후에 다시 분석 할 것 이다.
mComponent Callbacks 가 언제 등록 되 었 는 지 다시 한 번 볼 게 요.
Application.java
public void registerComponentCallbacks(ComponentCallbacks callback) {
synchronized (mComponentCallbacks) {
mComponentCallbacks.add(callback);
}
}
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
synchronized (mComponentCallbacks) {
mComponentCallbacks.remove(callback);
}
}
위의 두 가지 방법 은 응용 프로그램 에서 정 의 된 것 으로 방법 에 따라 호출 하면 Context 기본 클래스 에서 호출 된 것 을 발견 할 수 있다.
/**
* Add a new {@link ComponentCallbacks} to the base application of the
* Context, which will be called at the same times as the ComponentCallbacks
* methods of activities and other components are called. Note that you
* <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
* appropriate in the future; this will not be removed for you.
*
* @param callback The interface to call. This can be either a
* {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
*/
public void registerComponentCallbacks(ComponentCallbacks callback) {
getApplicationContext().registerComponentCallbacks(callback);
}
/**
* Remove a {@link ComponentCallbacks} object that was previously registered
* with {@link #registerComponentCallbacks(ComponentCallbacks)}.
*/
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
getApplicationContext().unregisterComponentCallbacks(callback);
}
유출 경로 에 따 르 면 AwContents 에 mComponentCallbacks 가 등록 되 어 있 지 않 은 것 일 까?chromium 소스 코드 를 봐 야 진정한 이 유 를 알 수 있 습 니 다.다행히 chromium 은 오픈 소스 입 니 다.우 리 는 안 드 로 이 드 5.1 Chromium 소스 코드 에서 우리 가 필요 로 하 는 AwContents(자체 사다리)를 찾 았 습 니 다.언제 등 록 했 는 지 보 세 요.
AwContents.java
@Override
public void onAttachedToWindow() {
if (isDestroyed()) return;
if (mIsAttachedToWindow) {
Log.w(TAG, "onAttachedToWindow called when already attached. Ignoring");
return;
}
mIsAttachedToWindow = true;
mContentViewCore.onAttachedToWindow();
nativeOnAttachedToWindow(mNativeAwContents, mContainerView.getWidth(),
mContainerView.getHeight());
updateHardwareAcceleratedFeaturesToggle();
if (mComponentCallbacks != null) return;
mComponentCallbacks = new AwComponentCallbacks();
mContext.registerComponentCallbacks(mComponentCallbacks);
}
@Override
public void onDetachedFromWindow() {
if (isDestroyed()) return;
if (!mIsAttachedToWindow) {
Log.w(TAG, "onDetachedFromWindow called when already detached. Ignoring");
return;
}
mIsAttachedToWindow = false;
hideAutofillPopup();
nativeOnDetachedFromWindow(mNativeAwContents);
mContentViewCore.onDetachedFromWindow();
updateHardwareAcceleratedFeaturesToggle();
if (mComponentCallbacks != null) {
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
}
mScrollAccessibilityHelper.removePostedCallbacks();
mNativeGLDelegate.detachGLFunctor();
}
상기 두 가지 방법 에서 우 리 는 mComponent Callbacks 의 종적 을 발견 했다.onAttached ToWindow 에서 mContext.registerComponentCallbacks(mComponentCallbacks)를 호출 하여 등록 합 니 다.
onDetached FromWindow 에 역 등록 합 니 다.
onDetached FromWindow 의 코드 를 자세히 보면
onDetached FromWindow 에서 isDestroyed 조건 이 성립 되면 직접 return 합 니 다.이 로 인해 mContext.unregister ComponentCallbacks(mComponentCallbacks)를 실행 할 수 없습니다.
또한 우리 의 첫 번 째 누설 을 초래 할 수 있 습 니 다.onDetached FromWindow 가 정상 적 인 절 차 를 수행 하지 못 하고 ViewRootImp 의 dispatchDetached FromWindow 방법 을 사용 하지 않 기 때 문 입 니 다.이 조건 이 언제 true 가 될 지 찾 아 보 겠 습 니 다.
/**
* Destroys this object and deletes its native counterpart.
*/
public void destroy() {
mIsDestroyed = true;
destroyNatives();
}
destroy 에서 true 로 설 정 된 것 으로 밝 혀 졌 습 니 다.즉,destroy()를 실행 하면 등록 을 취소 할 수 없습니다.저 희 는 보통 activity 에서 webview 를 사용 할 때 onDestroy 방법 에서 mWebView.destroy()를 호출 합 니 다.웹 뷰 를 풀 어 줍 니 다.원본 코드 에 따 르 면 onDetached FromWindow 전에 destroy 를 호출 하면 제대로 등록 하지 못 하고 메모리 가 새 는 것 을 알 수 있 습 니 다.문제 의 해결
우 리 는 이 유 를 알 게 된 후에 해결 이 비교적 쉬 워 집 니 다.바로 webview 를 없 애기 전에 반드시 onDetached FromWindow 를 사용 해 야 합 니 다.우 리 는 먼저 webview 를 부모 view 에서 제거 한 다음 에 destroy 방법 을 호출 합 니 다.코드 는 다음 과 같 습 니 다.
@Override
protected void onDestroy() {
super.onDestroy();
if (mWebView != null) {
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.removeAllViews();
mWebView.destroy();
mWebView = null;
}
}
또 하나의 문 제 는 5.1 이하 의 기종 에 메모리 가 새 지 않 는 이유 입 니 다.4.4 의 소스 코드 AwContents 를 살 펴 보 겠 습 니 다.
/**
* @see android.view.View#onAttachedToWindow()
*
* Note that this is also called from receivePopupContents.
*/
public void onAttachedToWindow() {
if (mNativeAwContents == 0) return;
mIsAttachedToWindow = true;
mContentViewCore.onAttachedToWindow();
nativeOnAttachedToWindow(mNativeAwContents, mContainerView.getWidth(),
mContainerView.getHeight());
updateHardwareAcceleratedFeaturesToggle();
if (mComponentCallbacks != null) return;
mComponentCallbacks = new AwComponentCallbacks();
mContainerView.getContext().registerComponentCallbacks(mComponentCallbacks);
}
/**
* @see android.view.View#onDetachedFromWindow()
*/
public void onDetachedFromWindow() {
mIsAttachedToWindow = false;
hideAutofillPopup();
if (mNativeAwContents != 0) {
nativeOnDetachedFromWindow(mNativeAwContents);
}
mContentViewCore.onDetachedFromWindow();
updateHardwareAcceleratedFeaturesToggle();
if (mComponentCallbacks != null) {
mContainerView.getContext().unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
}
mScrollAccessibilityHelper.removePostedCallbacks();
if (mPendingDetachCleanupReferences != null) {
for (int i = 0; i < mPendingDetachCleanupReferences.size(); ++i) {
mPendingDetachCleanupReferences.get(i).cleanupNow();
}
mPendingDetachCleanupReferences = null;
}
}
onDetached FromWindow 방법 에 isDestroyed 라 는 판단 조건 이 없 는 것 을 볼 수 있 습 니 다.이것 은 바로 이 원인 으로 인 한 메모리 누 출 임 을 증명 합 니 다.문제 의 총화
웹 뷰 를 사용 하면 메모리 누 출 이 발생 하기 쉬 우 며,올 바 르 게 방출 되 지 않 으 면 oom 이 발생 하기 쉽다.웹 뷰 사용 에 도 구덩이 가 많 으 니 테스트 를 많이 해 야 합 니 다.
이상 의 안 드 로 이 드 5.1 WebView 메모리 유출 문제 와 빠 른 해결 방법 은 바로 편집장 이 여러분 에 게 공유 한 모든 내용 입 니 다.여러분 에 게 참고 가 되 고 저희 도 많이 응원 해 주시 기 바 랍 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Kotlin의 기초 - 2부지난 글에서는 Kotlin이 무엇인지, Kotlin의 특징, Kotlin에서 변수 및 데이터 유형을 선언하는 방법과 같은 Kotlin의 기본 개념에 대해 배웠습니다. 유형 변환은 데이터 변수의 한 유형을 다른 데이터...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.