Android WebView硬件渲染奇怪的工件问题

时间:2013-06-14 00:35:27

标签: android css html5 android-webview

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

渲染错误(在Samsung Galaxy Nexus上运行): Image showing weird rending issue.

正确渲染(在Motorola Bionic上运行): How it should render.

Android Manifest:          

    <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

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

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

更新3

我已经为你做了一个测试项目。使用缩放级别并移动窗口。如果有什么奇怪的话,请告诉我。到目前为止,这是使用Android 4.2.2

http://jsbin.com/equtoq/2

谢谢!

4 个答案:

答案 0 :(得分:8)

经过多次错误启动后,我只能找到硬件渲染和软件渲染之间切换的hackish解决方案,具体取决于当前的缩放。

基本上这个想法是渲染工件只会在一定的缩放后出现,因此我们可以在超过此阈值之前安全地使用硬件渲染,并在使用更大的缩放时切换到速度较慢但正确的软件渲染。缩放阈值>虽然您可能需要检查Galaxy Nexus以查看此值是否为通用值,但在N7上为1.67。根据我的经验,每个版本的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()调用的次数到每个缩放一次的事件,以减少将在短暂间隔内显示的硬件/软件渲染工件

答案 1 :(得分:6)

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

KUDOS DELYAN!

答案 2 :(得分:1)

确保在Manifest XML中启用硬件加速。

<application android:hardwareAccelerated="true" ... />

答案 3 :(得分:0)

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

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

他们是我所知道的两个解决方案。如果你真的不知道这个变化对你的应用程序意味着什么,我不建议你继续使用android:configChanges作为它通常是有害的。如果你绝对需要保留android:configChanges,那么就有一个解决方案。您可以创建自己的View继承自WebView的自定义onConfigurationChanged。只需覆盖WebView,不要将其转发给超类..在这种情况下,它是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); } } 。我在下面列出了代码作为示例。

但更正确和正确的方法是纠正处理WebView状态的保存和恢复。如果你在网上搜索,你会发现很多解决这个问题的方法。这是一个特别好的。 http://www.devahead.com/blog/2012/01/preserving-the-state-of-an-android-webview-on-screen-orientation-change/

{{1}}