https://blog.csdn.net/qq_27381325/article/details/100162707
在 fragment 中 的webview 有的手机里 会闪退 报错信息如下:

chrome build fingerprint
rise I/Choreographer: Skipped 40 frames! The application may be doing too much work on its main thread.
This exception happens in the native code, there isn't much you can do in your Java code about it. Seems to be a graphic memory exhaustion, as it is crashing in malloc called from the GL library code. You say that the WebView is never destroyed, perhaps this is a result of memory fragmentation (the graphics code allocates / frees memory, and finally the memory map becomes so fragmented, that it's not possible to find a contiguous block of the requested size). I would suggest considering destroying and recreating WebView from time to time.
这种异常发生在本机代码中,在Java代码中对此无能为力。似乎是图形内存耗尽,因为它在从GL库代码调用的malloc中崩溃。您说WebView永远不会被破坏,这可能是内存碎片的结果(图形代码分配/释放内存,最后内存映射变得如此碎片化,以至于不可能找到具有请求大小的连续块)。我建议不时地考虑销毁和重新创建WebView。
Because Chrome usually runs GPU-related code in a separate process. So when it crashes, the main Chrome app process is not affected and continues to run. But in WebView everything is within a single app process, so crash in one component brings down the whole app.
因为Chrome通常在一个单独的进程中运行与gpu相关的代码。因此,当它崩溃时,主Chrome应用程序进程不受影响,并继续运行。但在WebView中,所有东西都在一个应用程序进程中,因此在一个组件中崩溃会导致整个应用程序崩溃。

内存占用 在大的 h5页面 增加比较多
web内存优化
1.获取系统分配的最大内存
<application
...
android:largeHeap="true"
...>
...
</application>
-
获取手机分配的内存 查看/system/build.prop文件内容:
image.png
对于本人这台手机,系统正常分配的内存最多为192M;当设置largeHeap时,最多可申请512M。当超过这个值时,就会出现OOM了
2.独立的web进程,与主进程隔开
这个方法被运用于类似qq,微信这样的超级app中,这也是解决任何webview内存问题屡试不爽的方法
对于封装的webactivity,在manifest.xml
中设置
<activity
android:name=".webview.WebViewActivity"
android:launchMode="singleTop"
android:process=":remote"
android:screenOrientation="unspecified" />
然后在关闭webactivity时销毁进程
@Overrideprotected
void onDestroy() {
super.onDestroy();
System.exit(0);}
关闭浏览器后便销毁整个进程,这样一般95%
的情况下不会造成内存泄漏之类的问题,但这就涉及到android进程间通讯,比较不方便处理, 优劣参半,也是可选的一个方案.
这个方案 测试里以后 确实不会再闪退
3.将webview的创建放在activity中用new WebView(getApplicationContext())的方式,并且修改webview的销毁为
@Override
protected void onDestroy() {
if( mWebView!=null) {
// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();
}
super.on Destroy();
4. 从根源解决(划重点)
前面的方法都没有解决我内存泄漏的问题,然后我看到了一篇文章是从源码角度分析了webview内存泄漏的原因,最后按作者的方法解决了问题,后面会贴上原文地址。这里简单说一下:
原文里说的webview引起的内存泄漏主要是因为org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。
org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow;系统会在attach和detach处进行注册和反注册component callback;
在onDetachedFromWindow() 方法的第一行中:
if (isDestroyed()) return;,
如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行unregister的操作;我们的activity退出的时候,都会主动调用 WebView.destroy() 方法,这会导致 isDestroyed() 返回 true;destroy()的执行时间又在onDetachedFromWindow之前,所以就会导致不能正常进行unregister()。
然后解决方法就是:让onDetachedFromWindow先走,在主动调用destroy()之前,把webview从它的parent上面移除掉。
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.destroy();
完整的activity的onDestroy()方法:
@Override
protected void onDestroy() {
if( mWebView!=null) {
// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();
}
super.on Destroy();
}
如果以上的方案还不管用,实时加入反射来清理webview的引用:
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) {
}
}
然后在activity中加入
public void onCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setConfigCallback((WindowManager);
getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
}
publicvoidonDestroy()
{
setConfigCallback(null);
super.onDestroy();
}
在activity 中加入 并没有作用
- 对于application 级别的可能的misbehaving callbacks,加入
private static String[] misbehavingClasses = new String[]{
"com.google.android.gms.ads",
"com.android.org.chromium.android_webview.AwContents$AwComponentCallbacks",
};
public static boolean isMisbehavingCallBacks(String name){
for(String s : misbehavingClasses){
if(name.startsWith(s)){
return true;
}
}
return false;
}
然后重写applicaiton类记录这些callback, 并在适当的时机删掉:
@Override
public void registerComponentCallbacks(ComponentCallbacks callback) {
super.registerComponentCallbacks(callback);
ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.onComponentCallbacksRegistered(callback);
}
@Override
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.onComponentCallbacksUnregistered(callback);
super.unregisterComponentCallbacks(callback);
}
public void forceUnregisterComponentCallbacks() {
ComponentCallbacksBehavioralAdjustmentToolIcs.INSTANCE.unregisterAll(this);
}
private static class ComponentCallbacksBehavioralAdjustmentToolIcs {
private static final String TAG = "componentCallbacks";
static ComponentCallbacksBehavioralAdjustmentToolIcs INSTANCE = new ComponentCallbacksBehavioralAdjustmentToolIcs();
private WeakHashMap<ComponentCallbacks, ApplicationErrorReport.CrashInfo> mCallbacks = new WeakHashMap<>();
private boolean mSuspended = false;
public void onComponentCallbacksRegistered(ComponentCallbacks callback) {
Throwable thr = new Throwable("Callback registered here.");
ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(thr);
if (FApplication.DEBUG) DLog.w(TAG, "registerComponentCallbacks: " + callback.getClass().getName(), thr);
if (!mSuspended) {
if (BugFix.isMisbehavingCallBacks(callback.getClass().getName())) {
mCallbacks.put(callback, ci);
}
// TODO: other classes may still prove to be problematic? For now, only watch for .gms.ads, since we know those are misbehaving
} else {
if (FApplication.DEBUG) DLog.e(TAG, "ComponentCallbacks was registered while tracking is suspended!");
}
}
public void onComponentCallbacksUnregistered(ComponentCallbacks callback) {
if (!mSuspended) {
if (FApplication.DEBUG) {
DLog.i(TAG, "unregisterComponentCallbacks: " + callback, new Throwable());
}
mCallbacks.remove(callback);
}
}
public void unregisterAll(Context context) {
mSuspended = true;
for (Map.Entry<ComponentCallbacks, ApplicationErrorReport.CrashInfo> entry : mCallbacks.entrySet()) {
ComponentCallbacks callback = entry.getKey();
if (callback == null) continue;
if (FApplication.DEBUG) {
DLog.w(TAG, "Forcibly unregistering a misbehaving ComponentCallbacks: " + entry.getKey());
DLog.w(TAG, entry.getValue().stackTrace);
}
try {
context.unregisterComponentCallbacks(entry.getKey());
} catch (Exception exc) {
if (FApplication.DEBUG) DLog.e(TAG, "Unable to unregister ComponentCallbacks", exc);
}
}
mCallbacks.clear();
mSuspended = false;
}
}
看日志过程 发现 rxbus 会发通知。通知更新 三个Tab。
webview.reload 同时执行了 三次。webview加载了三个页面 内存超过了
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
systemWebView.reload();
}
},2000);
加入延时 不再 有闪退的问题
RxBus 接受通知时 如果页面没有在 最上面 也会执行。
使用Rxbus通知Activity页面刷新,不立刻刷新,延迟到activity onResume 后才刷新
RxBus.getDefault().toObservable(ReportUpdateEvent.class)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
//延迟收到刷新消息
.compose(new Observable.Transformer<ReportUpdateEvent, ReportUpdateEvent>() {
@Override
public Observable<ReportUpdateEvent> call(Observable<ReportUpdateEvent> reportUpdateEventObservable) {
Func1<ActivityEvent, Boolean> func1=new Func1<ActivityEvent, Boolean>() {
@Override
public Boolean call(ActivityEvent activityEvent) {
return activityEvent.equals(ActivityEvent.RESUME);
}
};
//延迟发送事件给订阅者,直到收到activity的Resume事件
Observable<ActivityEvent> o=lifecycleProvider.lifecycle().takeFirst(func1);
//delay 除了可以设置时间,还可以放观察者
return reportUpdateEventObservable.delay(new Func1<ReportUpdateEvent, Observable<ActivityEvent>>() {
@Override
public Observable<ActivityEvent> call(ReportUpdateEvent reportUpdateEvent) {
//o是activity生命周期的观察者
return o;
}
});
}
})
.subscribe(event -> {
Log.e("MyReportListViewModel","收到刷新事件");
if (event.isRefresh()) {
// 新增报事成功后,返回刷新报事页面。
requestData(1);
}
}, throwable -> {
2.优化一下代码
RxBus.getDefault().toObservable(ReportUpdateEvent.class)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
//延迟收到刷新消息
.compose(RxUtil.delayTransformerByLife(lifecycleProvider))
.subscribe(event -> {
Log.e("MyReportListViewModel","收到刷新事件");
if (event.isRefresh()) {
// 新增报事成功后,返回刷新报事页面。
requestData(1);
}
}, throwable -> {
})
public class RxUtil {
public static <T> Observable.Transformer<T, T> delayTransformerByLife(LifecycleProvider<?> lifecycleProvider){
return observable-> observable.delay((o)->{
return lifecycleProvider.lifecycle().takeFirst(((activityEvent)->{
return activityEvent.equals(ActivityEvent.RESUME);
}));
});
}
}
扩展
1.在定时器中加入了下面这个内存检测,当超出这个内存的时候做一些操作,避免crash 并且在定时器中加入内存检测
/**
* 检查当前内存,占用量超过(mKeyMemory*60)%,返回错误。
*/
public boolean checkMemory() {
ActivityManager mSystemService = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mMemoryInfo = new ActivityManager.MemoryInfo();
mSystemService.getMemoryInfo(mMemoryInfo);
long availMem = mMemoryInfo.availMem;
long totalMem = mMemoryInfo.totalMem;
float result = (float) (availMem * 1.0 / totalMem);
Log.e("MoreTasks", " there is percent memory now is " + result);
if (result >= 0.6) {
return false;
}
return true;
}
2.内存泄漏检测工具leakcanary
3.封装过的webview
相比系统内置的webview的支持自2005年之后就没有了,而首推google的chrome。 腾讯的x5webview对h5的兼容性与稳定性与安全性逐渐凸显出来,并自成一系, 下面以使用x5webview为例做说明:
- 首先使用webview的时候,不在xml里面声明,而是直接代码new个对象,传入application context防止activity引用滥用.
webView = new BridgeWebView(getContext().getApplicationContext());
webFrameLayout.addView(webView, 0);
在使用了这个方式后,基本上90%的webview内存泄漏的问题便得以解决.
5. Android WebView缓存机制和性能优化
https://blog.csdn.net/weixin_45365889/article/details/100542449
4.刷新webview 方法尝试 有的没有作用
mWebView.loadUrl("http://www.websitehere.php");
webView.loadUrl( "javascript:window.location.reload( true )" );
this has helped me, webview.reload was not working me, But this needs,
yourWebView.getSettings().setJavaScriptEnabled(true);
enabled in webview Settings
try this :
mWebView.loadUrl(mWebView.getUrl().toString());
String url = mWebView.getUrl();
String postData = MyUtility.getOptionsDataForPOSTURL(mContext);
mWebView.postUrl(url, EncodingUtils.getBytes(postData, "BASE64"));
Override onFormResubmission
in WebViewClient
@Override
public void onFormResubmission(WebView view, Message dontResend, Message resend){
resend.sendToTarget();
}
public void reload() {
if (!AgentWebUtils.isUIThread()) {
mHandler.post(new Runnable() {
@Override
public void run() {
reload();
}
});
return;
}
this.mWebView.reload();
}
5.为方便webactivity的debug
在application的oncreate里面,我们加入
private static void enableStrictMode() {
StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog();
StrictMode.VmPolicy.Builder vmPolicyBuilder =
new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog();
threadPolicyBuilder.penaltyFlashScreen();
vmPolicyBuilder.setClassInstanceLimit(WebActivity.class, 1);
StrictMode.setThreadPolicy(threadPolicyBuilder.build());
StrictMode.setVmPolicy(vmPolicyBuilder.build());
}
在webview中,对于android4.4以上的版本,我们开启调试模式
if (FApplication.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
最后不可缺少的是, leakcanary的加入来跟踪这些消耗内存的组件:
在webfragment,webactivity,webview ondestory后加上
RefWatcher refWatcher = FApplication.getRefWatcher();
refWatcher.watch(obj);
网友评论