Подтвердить что ты не робот

Android WebView Hardware Rendering Weird Artifact Issue

На некоторых устройствах, таких как Nexus 7 или Samsung 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

Похоже, насколько мне известно, что это происходит только на устройствах Google (таких как Nexus). Итак, может быть ошибка в их ядре или видеодрайвере? Или это может быть что-то не так с Android 4.2.2.

Если у вас есть идеи по устранению этого вопроса, сообщите мне.

ОБНОВЛЕНИЕ 3

Я сделал тестовый проект для вас. Играйте с уровнем масштабирования и перемещением окна вокруг. Дай мне знать, если это будет что-то странное. Это пока что с Android 4.2.2

http://jsbin.com/equtoq/2

Спасибо!

4b9b3361

Ответ 1

Спасибо Деляну за решение проблемы. Как он сказал мне, чтобы решить это, нужно добавить -webkit-transform: translate3d(0,0,0) ко всем движущимся частям.

КУДОС ДЕЛЬЯН!

Ответ 2

После нескольких ложных запусков я мог найти только хакерское решение переключения между рендерингом оборудования и программным рендерингом в зависимости от текущего масштабирования.

По сути, идея заключается в том, что рендеринг артефактов будет отображаться только после определенного масштабирования, поэтому мы сможем безопасно использовать аппаратное рендеринг до того, как этот порог будет пересечен, и переключитесь на более медленное, но корректное рендеринг программного обеспечения при использовании большего масштабирования. Пороговое значение масштабирования составляет 1,67 на N7, хотя вам, возможно, придется проверить Galaxy Nexus, чтобы узнать, универсально ли это значение. По моему опыту, существует множество проблем с WebView для каждой версии Android, поэтому это может быть лучшим решением в настоящее время, пока все не будут обновлены до 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() до одного -per-zoom, чтобы уменьшить артефакты аппаратного и программного обеспечения, которые будут отображаться в течение короткого интервала

Ответ 3

Убедитесь, что аппаратное ускорение включено в вашем манифесте XML.

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

Ответ 4

Это ошибка гонки в WebView. Если рендеринг страницы становится несовместимым с фактическим просмотром страницы.

Нормальная причина в том, что вы обрабатываете поворот с помощью android:configChanges в тэге <activity> в файле AndroidManifest.xml. Воссоздание 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);
    }
}