WebView의 메모리 누수
WebView가 포함 된 xml 레이아웃을 사용하는 활동이 있습니다. 내 활동 코드에서 WebView를 전혀 사용하지 않고 있으며, 내 xml 레이아웃에 앉아서 표시되는 것뿐입니다.
이제 활동을 마쳤을 때 내 활동이 기억에서 지워지지 않는 것을 발견했습니다. (hprof 덤프를 통해 확인합니다). xml 레이아웃에서 WebView를 제거하면 활동이 완전히 지워집니다.
나는 이미 시도
webView.destroy();
webView = null;
내 활동의 onDestroy ()에 있지만 그다지 도움이되지 않습니다.
내 hprof 덤프에서 내 활동 ( 'Browser'라는 이름)에는 다음과 같은 나머지 GC 루트가 있습니다 (호출 한 후 destroy()
).
com.myapp.android.activity.browser.Browser
- mContext of android.webkit.JWebCoreJavaBridge
- sJavaBridge of android.webkit.BrowserFrame [Class]
- mContext of android.webkit.PluginManager
- mInstance of android.webkit.PluginManager [Class]
다른 개발자도 비슷한 경험을했습니다. http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/ 에서 Filipe Abrantes의 답변을 참조 하세요.
실제로 매우 흥미로운 게시물입니다. 최근에 Android 앱에서 메모리 누수 문제를 해결하는 데 매우 어려움을 겪었습니다. 결국 내 xml 레이아웃에는 사용되지 않더라도 화면 회전 / 앱 다시 시작 후 메모리가 g- 수집되는 것을 방지하는 WebView 구성 요소가 포함되어 있음이 밝혀졌습니다. 이것은 현재 구현의 버그입니까, 아니면 뭔가 있습니까? WebViews를 사용할 때 수행해야하는 특정
안타깝게도 아직이 질문에 대한 블로그 나 메일 링리스트에 답글이 없습니다. 따라서 SDK의 버그 ( http://code.google.com/p/android/issues/detail?id=2181 보고 된 MapView 버그와 유사 할 수 있음 ) 또는 전체 활동을 얻는 방법이 궁금합니다. webview가 내장 된 메모리를 제거 하시겠습니까?
위의 의견과 추가 테스트를 통해 문제가 SDK의 버그라고 결론을 내립니다. XML 레이아웃을 통해 WebView를 만들 때 활동은 응용 프로그램 컨텍스트가 아닌 WebView의 컨텍스트로 전달됩니다. 활동을 완료 할 때 WebView는 활동에 대한 참조를 계속 유지하므로 활동이 메모리에서 제거되지 않습니다. 이에 대한 버그 보고서를 제출했습니다. 위의 주석에있는 링크를 참조하십시오.
webView = new WebView(getApplicationContext());
이 해결 방법은 특정 사용 사례에서만 작동합니다. 즉, href 링크 나 대화 상자에 대한 링크 등없이 웹보기에 html을 표시해야하는 경우에만 작동합니다. 아래 설명을 참조하십시오.
이 방법으로 운이 좋았습니다.
xml에 FrameLayout을 컨테이너로 넣고 web_container라고 부릅니다. 그런 다음 위에서 언급 한대로 WebView를 프로그래밍 방식으로 광고합니다. onDestroy, FrameLayout에서 제거하십시오.
이것이 xml 레이아웃 파일의 어딘가에 있다고 가정합니다. 예 : layout / your_layout.xml
<FrameLayout
android:id="@+id/web_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
그런 다음 뷰를 확장 한 후 응용 프로그램 컨텍스트로 인스턴스화 된 WebView를 FrameLayout에 추가합니다. onDestroy, webview의 destroy 메소드를 호출하고 뷰 계층 구조에서 제거하지 않으면 누출됩니다.
public class TestActivity extends Activity {
private FrameLayout mWebContainer;
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
mWebContainer = (FrameLayout) findViewById(R.id.web_container);
mWebView = new WebView(getApplicationContext());
mWebContainer.addView(mWebView);
}
@Override
protected void onDestroy() {
super.onDestroy();
mWebContainer.removeAllViews();
mWebView.destroy();
}
}
또한 FrameLayout과 layout_width 및 layout_height는 작동하는 기존 프로젝트에서 임의로 복사되었습니다. 다른 ViewGroup이 작동한다고 가정하고 다른 레이아웃 치수가 작동한다고 확신합니다.
이 솔루션은 FrameLayout 대신 RelativeLayout에서도 작동합니다.
다음은 위의 해킹을 사용하여 메모리 누수를 원활하게 방지하는 WebView의 하위 클래스입니다.
package com.mycompany.view;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* see http://stackoverflow.com/questions/3130654/memory-leak-in-webview and http://code.google.com/p/android/issues/detail?id=9375
* Note that the bug does NOT appear to be fixed in android 2.2 as romain claims
*
* Also, you must call {@link #destroy()} from your activity's onDestroy method.
*/
public class NonLeakingWebView extends WebView {
private static Field sConfigCallback;
static {
try {
sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
sConfigCallback.setAccessible(true);
} catch (Exception e) {
// ignored
}
}
public NonLeakingWebView(Context context) {
super(context.getApplicationContext());
setWebViewClient( new MyWebViewClient((Activity)context) );
}
public NonLeakingWebView(Context context, AttributeSet attrs) {
super(context.getApplicationContext(), attrs);
setWebViewClient(new MyWebViewClient((Activity)context));
}
public NonLeakingWebView(Context context, AttributeSet attrs, int defStyle) {
super(context.getApplicationContext(), attrs, defStyle);
setWebViewClient(new MyWebViewClient((Activity)context));
}
@Override
public void destroy() {
super.destroy();
try {
if( sConfigCallback!=null )
sConfigCallback.set(null, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected static class MyWebViewClient extends WebViewClient {
protected WeakReference<Activity> activityRef;
public MyWebViewClient( Activity activity ) {
this.activityRef = new WeakReference<Activity>(activity);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
final Activity activity = activityRef.get();
if( activity!=null )
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}catch( RuntimeException ignored ) {
// ignore any url parsing exceptions
}
return true;
}
}
}
그것을 사용하려면 레이아웃에서 WebView를 NonLeakingWebView로 바꾸십시오.
<com.mycompany.view.NonLeakingWebView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
...
/>
그런 다음 NonLeakingWebView.destroy()
활동의 onDestroy 메서드에서 호출해야합니다 .
이 웹 클라이언트는 일반적인 경우를 처리해야하지만 일반 웹 클라이언트만큼 모든 기능을 갖추고 있지 않을 수 있습니다. 예를 들어 플래시와 같은 것에 대해서는 테스트하지 않았습니다.
이 게시물 ( https://stackoverflow.com/a/12408703/1369016 ) 에 대한 user1668939의 답변을 기반으로 , 이것이 조각 내부의 WebView 누출을 수정 한 방법입니다.
@Override
public void onDetach(){
super.onDetach();
webView.removeAllViews();
webView.destroy();
}
user1668939의 답변과 다른 점은 자리 표시자를 사용하지 않았다는 것입니다. WebvView 참조 자체에서 removeAllViews ()를 호출하는 것만으로도 문제가 해결되었습니다.
## 업데이트 ##
나와 비슷하고 여러 조각 안에 WebView가 있고 모든 조각에서 위의 코드를 반복하지 않으려면 리플렉션을 사용하여 해결할 수 있습니다. Fragments가 이것을 확장하도록 만드십시오.
public class FragmentWebViewLeakFree extends Fragment{
@Override
public void onDetach(){
super.onDetach();
try {
Field fieldWebView = this.getClass().getDeclaredField("webView");
fieldWebView.setAccessible(true);
WebView webView = (WebView) fieldWebView.get(this);
webView.removeAllViews();
webView.destroy();
}catch (NoSuchFieldException e) {
e.printStackTrace();
}catch (IllegalArgumentException e) {
e.printStackTrace();
}catch (IllegalAccessException e) {
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}
}
I am assuming you are calling your WebView field "webView" (and yes, your WebView reference must be a field unfortunately). I have not found another way to do it that would be independent from the name of the field (unless I loop through all the fields and check if each one is from a WebView class, which I do not want to do for performance issues).
After reading http://code.google.com/p/android/issues/detail?id=9375, maybe we could use reflection to set ConfigCallback.mWindowManager to null on Activity.onDestroy and restore it on Activity.onCreate. I'm unsure though if it requires some permissions or violates any policy. This is dependent on android.webkit implementation and it may fail on later versions of Android.
public void setConfigCallback(WindowManager windowManager) {
try {
Field field = WebView.class.getDeclaredField("mWebViewCore");
field = field.getType().getDeclaredField("mBrowserFrame");
field = field.getType().getDeclaredField("sConfigCallback");
field.setAccessible(true);
Object configCallback = field.get(null);
if (null == configCallback) {
return;
}
field = field.getType().getDeclaredField("mWindowManager");
field.setAccessible(true);
field.set(configCallback, windowManager);
} catch(Exception e) {
}
}
Calling the above method in Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
}
public void onDestroy() {
setConfigCallback(null);
super.onDestroy();
}
I fixed memory leak issue of frustrating Webview like this:
(I hope this may help many)
Basics:
- To create a webview, a reference (say an activity) is needed.
- To kill a process:
android.os.Process.killProcess(android.os.Process.myPid());
can be called.
Turning point:
By default, all activities run in same process in one application. (the process is defined by package name). But:
Different processes can be created within same application.
Solution: If a different process is created for an activity, its context can be used to create a webview. And when this process is killed, all components having references to this activity (webview in this case) are killed and the main desirable part is :
GC is called forcefully to collect this garbage (webview).
Code for help: (one simple case)
Total two activities: say A & B
Manifest file:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:process="com.processkill.p1" // can be given any name
android:theme="@style/AppTheme" >
<activity
android:name="com.processkill.A"
android:process="com.processkill.p2"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.processkill.B"
android:process="com.processkill.p3"
android:label="@string/app_name" >
</activity>
</application>
Start A then B
A > B
B is created with webview embedded.
When backKey is pressed on activity B, onDestroy is called:
@Override
public void onDestroy() {
android.os.Process.killProcess(android.os.Process.myPid());
super.onDestroy();
}
and this kills the current process i.e. com.processkill.p3
and takes away the webview referenced to it
NOTE: Take extra care while using this kill command. (not recommended due to obvious reasons). Don't implement any static method in the activity (activity B in this case). Don't use any reference to this activity from any other (as it will be killed and no longer available).
You can try putting the web activity in a seperate process and exit when the activity is destroyed, if multiprocess handling is not a big effort to you.
You need to remove the WebView from the parent view before calling WebView.destroy()
.
WebView's destroy() comment - "This method should be called after this WebView has been removed from the view system."
There is an issue with "app context" workaround: crash when WebView
tries to show any dialog. For example "remember the password" dialog on login/pass forms submition (any other cases?).
It could be fixed with WebView
settings' setSavePassword(false)
for the "remember the password" case.
참고URL : https://stackoverflow.com/questions/3130654/memory-leak-in-webview
'Program Tip' 카테고리의 다른 글
Android : API 수준 VS. (0) | 2020.10.24 |
---|---|
벡터 중 하나만 사용하는 기준을 사용하여 동일한 방식으로 두 벡터를 정렬하려면 어떻게해야합니까? (0) | 2020.10.24 |
CSS에서 'property : 0'또는 'property : 0px'? (0) | 2020.10.24 |
$ {}와 # {}의 차이점은 무엇입니까? (0) | 2020.10.24 |
Angular 2-라우팅-Observable로 CanActivate 작업 (0) | 2020.10.23 |