13

在 Nexus 7 或三星 Galaxy Nexus(可能还有其他设备)等设备上,我注意到了这个问题(见图)。这是在 WebView 中的硬件加速模式下运行的。但是,当我将其设置为软件渲染模式时,它显示正常,但会稍微降低性能。我很想学习如何解决这个问题,并且只在 WebViews 上使用硬件加速而不是软件模式。

渲染不良(在三星 Galaxy Nexus 上运行): 显示奇怪的撕裂问题的图像。

正确的渲染(在 Motorola Bionic 上运行): 它应该如何渲染。

安卓清单:

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="17" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <supports-screens
       android:resizeable="true"
       android:smallScreens="true" 
       android:normalScreens="true" 
       android:largeScreens="true"
       android:xlargeScreens="true"
       android:anyDensity="true" />

    <application
        android:allowBackup="true"
        android:hardwareAccelerated="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        ....
    </application>
</manifest>

Java代码:

....
@TargetApi(VERSION_CODES.HONEYCOMB)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        try{

            db = new DatabaseHandler(this);
            tts = new TextToSpeech(this, this);
            handler = new Handler();

            frame = new FrameLayout(this);
            frame.setBackgroundColor(Color.GRAY);

            wv = new WebView(this);
            wv.setScrollBarStyle(WebView.SCROLLBARS_INSIDE_OVERLAY);

            frame.addView(wv);

            wv.setVisibility(WebView.INVISIBLE);

            iv = new ImageView(this);
            Drawable d = Drawable.createFromStream(getAssets().open("www/img/loading.jpg"), null);
            iv.setImageDrawable(d);

            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                    FrameLayout.LayoutParams.MATCH_PARENT);
            iv.setLayoutParams(params);

            frame.addView(iv);

            WebSettings ws = wv.getSettings();
            ws.setRenderPriority(RenderPriority.HIGH);
            ws.setJavaScriptEnabled(true);
            ws.setAllowFileAccess(true);
            if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) 
                  ws.setAllowUniversalAccessFromFileURLs(true);
            ws.setCacheMode(WebSettings.LOAD_NO_CACHE);
            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
            if(Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB && !pref.getBoolean("prefHardware", true)){
                wv.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
            }
            wv.setWebChromeClient(new WebChromeClient(){
                //int id = 0;
                @Override
                public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
                    // TODO Auto-generated method stub
//                  Log.d("JS Console", consoleMessage.message() + " at " + consoleMessage.sourceId()  + ":" + consoleMessage.lineNumber());
                    //Toast.makeText(TaskRunner.this, consoleMessage.message() + " at " + consoleMessage.sourceId()  + ":" + consoleMessage.lineNumber(), Toast.LENGTH_LONG).show();
//                  NotificationCompat.Builder mBuilder =
//                          new NotificationCompat.Builder(TaskRunner.this)
//                          .setSmallIcon(R.drawable.ic_launcher)
//                          .setContentTitle("Console Message")
//                          .setContentText(consoleMessage.message() + " at " + consoleMessage.sourceId()  + ":" + consoleMessage.lineNumber());
////                    mBuilder.setContentIntent(null);
//                  NotificationManager mNotificationManager =
//                      (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//                  // mId allows you to update the notification later on.
//                  mNotificationManager.notify(id++, mBuilder.build());
                    return super.onConsoleMessage(consoleMessage);
                }
            });

            wv.setWebViewClient(new WebViewClient(){
                @Override
                public void onPageFinished(WebView view, String url) {
                    // TODO Auto-generated method stub
                    //Log.i("TEST", "TEST");
                    //hideLoading();
                    super.onPageFinished(view, url);
                }

                @Override
                public void onPageStarted(WebView view, String url,
                        Bitmap favicon) {
                    // TODO Auto-generated method stub
                    if(frame.getChildAt(frame.getChildCount()-1) != iv){
                        frame.addView(iv);
                        wv.setVisibility(WebView.INVISIBLE);
                    }
                    super.onPageStarted(view, url, favicon);
                }

                @Override
                public void onReceivedError(WebView view, int errorCode,
                        String description, String failingUrl) {
                    // TODO Auto-generated method stub
                    Log.e("ERROR", description);
                    super.onReceivedError(view, errorCode, description, failingUrl);
                }
            });
            MyJavascriptInterface ji = new MyJavascriptInterface();
            wv.addJavascriptInterface(ji, "ji");

            setOrientation();

            wv.loadUrl("file:///android_asset/www/index.html");

            setContentView(frame);

        }
        catch(Exception e){

        }

    }
    ....

HTML 文件:

<!DOCTYPE html>
<html>
<head>
    <title>Task Player</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="format-detection" content="telephone=no" />
    ......
</head>
<body>
    <div id="gamespacer">
        <div id="instructions-block">
            <div id="explaination">
            </div>
            <div id="instructions-close">
                <br /><br />
                <div class="btn btn-large btn-info" id="speak-tts">Speak</div>
                <br /><br />
                <input type='checkbox' id='auto-speak' value="yes" checked="checked" />Auto speak
            </div>

            <div id="drag-container">
                <div id='drag'>
                    <span id='au'><img src="img/arrow-up.png" alt="" width="45" /></span>
                    <span id='ad'><img src="img/arrow-down.png" alt="" width="45"/></span>
                </div>
            </div>
        </div>
    </div>
    <div id="play-container">
            <canvas>
            </canvas>
    </div>
    <div class="centerMe" id="rotate" style="display: none;"><p style="font-size: 24px; margin-top: 20px">Rotate the device to play the game.</p></div>
    <div id="winScreen">
        <div id="points">
            You Got<br/> 
            <div id="mypoints">0</div> 
            Points
        </div>
        <a href="#" id='done' class="btn btn-large btn-inverse">Done</a>
    </div>
....
</body>
</html>

CSS:

#winScreen{
  display: none;
  width: 100%;
  height: 100%;
  text-align: center;
  background-color: green;
  font-size: 24px;
  margin: 0 auto;
  position: absolute;
  left: 0px;
  top: 0px;
}
  #points {
    padding-top: 110px;
    color: yellow;
    margin-bottom: 10px;
  }

  #mypoints{
    margin: 10px;
  }

  .button{
    margin: 0 auto;
    margin-top: 50px;
  }

#play-container{  
  width: 100%;
  height: 100%;
  margin: 0 auto;
  text-align: center;
}

canvas {
    background-color: white;
    position: absolute;
    top: 0px;
    left: 0px;
}

#gamespacer {
}

      #instructions-block {
            position: absolute;
            left: 0px;
            top: -144px;
            width: 100%;
            background-color: white;
            z-index: 1;
        }

        #instructions-content {
          margin-top: 5px;
        }

        #instructions-close {
          text-align: center;
          margin-bottom: 10px;
        }

        #explaination {
          margin: 5px;
          font-size: 24px;
          line-height: 1;
          position: relative;
          width: 100%
          height: 100%
        }


        #drag-container{
            width: 100%;
        }

        #drag {
            position: absolute;
            left: 92%;
            margin-top: 5px;
        }

#au{
    display: none;
    pointer-events: none;
}    

#ad {
    pointer-events: none;
}

body{

   -webkit-user-select: none;
  user-select: none;


  -webkit-touch-callout: none;
  touch-callout: none;

  -webkit-tap-highlight-color: rgba(0,0,0,0);
  tap-highlight-color: rgba(0,0,0,0); 

}

更新 1

所以我尝试了以下代码,但效果不佳。

wv.setLayerType(View.LAYER_TYPE_HARDWARE, null);

更新 2

到目前为止,据我所知,这只发生在 Google 制造的设备(如 Nexus)上。那么,他们的内核或视频驱动程序中是否存在错误?或者,Android 4.2.2 可能有问题。

如果您对如何解决此问题有任何想法,请告诉我。

更新 3

我做了一个测试项目供你尝试。玩弄缩放级别并移动窗口。让我知道它是否有什么奇怪的地方。到目前为止,它使用 Android 4.2.2

http://jsbin.com/equtoq/2

谢谢!

4

4 回答 4

8

在多次错误开始之后,我只能找到根据当前缩放在硬件渲染和软件渲染之间切换的骇人听闻的解决方案。

基本上这个想法是渲染伪影只会在一定的缩放后出现,所以我们可以在超过这个阈值之前安全地使用硬件渲染,并在使用更大的缩放时切换到更慢但正确的软件渲染。N7 上的缩放阈值 > 1.67,但您可能需要检查 Galaxy Nexus 以查看此值是否通用。根据我的经验,每个 Android 版本的 WebView 都存在很多问题,所以这可能是目前最好的解决方案,直到每个人都更新到 4.3...

 webView.setWebViewClient(new WebViewClient() {

                @Override
                public void onScaleChanged(WebView view, float oldScale,
                        float newScale) {
                //4.3 SDK required, or change Build.VERSION_CODES.JELLY_BEAN_MR2 to 18
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    if (newScale > 1.7f) {
                        if (webView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
                            webView.setLayerType(View.LAYER_TYPE_SOFTWARE,
                                    null);
                        }
                    } else {
                        if (webView.getLayerType() != View.LAYER_TYPE_HARDWARE) {
                            webView.setLayerType(View.LAYER_TYPE_HARDWARE,
                                    null);
                        }
                    }
                }
                }
});

请注意,这onScaleChanged()会被多次调用,有时会通过一次缩放(使用放大/缩小按钮)越过阈值,因此您应该添加一些逻辑来减少对每个缩放事件一次的 webView.setLayerType() 调用次数减少将在短时间内显示的硬件/软件渲染伪影

于 2013-07-27T06:23:43.910 回答
6

感谢 Delyan 解决了这个问题。他告诉我如何解决它是添加-webkit-transform: translate3d(0,0,0)到所有移动的部分。

荣誉德利安!

于 2013-07-28T20:03:14.937 回答
1

确保在您的清单 XML 中启用硬件加速。

<application android:hardwareAccelerated="true" ... />
于 2013-06-14T17:13:27.770 回答
0

这是 WebView 中的竞争条件错误。页面的呈现与页面的实际视图不同步的地方。

正常原因是因为您在文件android:configChanges中的<activity>标签中处理旋转。AndroidManifest.xml重新创建 on轮换WebView将解决此问题,但这本身还有其他问题。

他们是我知道的两种解决方案。android:configChanges如果您真的不知道此更改对您的应用程序意味着什么,我不建议您继续使用它通常是有害的。如果您绝对需要保留android:configChanges,有一个解决方案。您可以创建自己的自定义View继承自WebView. 只需覆盖onConfigurationChanged并且不要将其转发给超类..在这种情况下就是WebView. 我在下面包含了代码作为示例。

但更真实正确的做法是正确处理WebView的状态的保存和恢复。如果您在网上四处寻找,您会找到许多解决此问题的方法。这是一个特别好的。http://www.devahead.com/blog/2012/01/preserving-the-state-of-an-android-webview-on-screen-orientation-change/

import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.webkit.WebView;


public class MyWebView extends WebView
{
    public MyWebView(Context context)
    {
        super(context);
    }

    public MyWebView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public MyWebView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig)
    {
        // NOTE: We don't want to call this
        // super.onConfigurationChanged(newConfig);
    }
}
于 2013-07-27T22:36:04.900 回答