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