I need to enable hardware acceleration on one of my WebViews. So far, I've found out that if I build my project with a target API of 11 or 13 (Android 3.0 and 3.2, respectively), hardware acceleration gets enabled and everything's fine. But the weird part is that when I build my project with API 17 or 18, all my efforts to turn on hardware acceleration get ignored for some reason.
So far I've tried:
1) Setting android:hardwareAccelerated="true" in my tag in the manifest
2) Same, but for the activity containing the WebView
3) The following code in my Activity's onCreate (before setting content view):
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
4) The same, but using getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
In all cases, webView.isHardwareAccelerated() returns false, even when it's working.
Please help, it would be pretty lame to be stuck on API 13 because of this...
Tested on API 18:
Perform the check by posting a Runnable to the WebView:
WebView webView = (WebView) findViewById(R.id.webview_id);
// Shows: "Is hardware accelerated? false"
// Toast.makeText(YourActivity.this, "Is hardware accelerated? " +
// webView.isHardwareAccelerated(),
// Toast.LENGTH_LONG).show();
webView.post(new Runnable() {
#Override
public void run() {
Shows: "Is hardware accelerated? true"
Toast.makeText(YourActivity.this, "Is hardware accelerated? " +
webView.isHardwareAccelerated(),
Toast.LENGTH_LONG).show();
if (webView.isHardwareAccelerated()) {
// isAccelerated();
} else {
// isNotAccelerated();
}
}
});
From resource page on Hardware Acceleration:
Hardware acceleration is enabled by default if your Target API level
is >=14, but can also be explicitly enabled.
If the image you are displaying is still pixelated, try setting:
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
and see if pixelation of the image gets better.
Consider reading the resource page on Hardware Acceleration.
Related
Is there a way to check if webview is available on the device?
Background:
If I add <uses-feature android:name="android.software.webview"/> to the Manifest the number of supported devices on Google Play drops from over 12,000 to less than 6,000 devices. So I added android:required="false" to this feature. In case webview is available websites should be displayed inside the app otherwise launched in the default browser:
String mUrl = "http://www.example.com";
if (*** WHAT TO PUT HERE? ***) {
WebView webview = (WebView) findViewById(R.id.webView);
if (Build.VERSION.SDK_INT >= 24) {
webview.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(request.toString());
return false;
}
});
} else {
webview.setWebViewClient(new WebViewClient() {
#SuppressWarnings("deprecation")
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return false;
}
});
}
webview.loadUrl(mUrl);
} else {
Uri uri = Uri.parse(mUrl);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
Edit (to make things clear): <uses-permission android:name="android.permission.INTERNET" /> is (and always had been) part of the manifest. It’s just the addition of <uses-feature android:name="android.software.webview" /> which causes the drop of supported devices.
There is someone having the same issue here: https://issuetracker.google.com/issues/37035282
(unfortunately not answered)
Although #vsatkh pointed out that it is not necessarily needed to declare this feature as required, you can check the device’s feature compatibility as follows:
getPackageManager().hasSystemFeature("android.software.webview")
This method returns true or false.
Some additional information about Google Play’s filtering:
Google Play only filters supported devices based on <uses-feature> elements declared in the manifest. <uses-permission> elements don’t affect Google Play’s filtering unless they imply a feature. android.permission.INTERNET does not imply any feature. Permissions that imply features are listed here:
https://developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions
Alternative solution:
val hasWebView: Boolean = kotlin.runCatching { CookieManager.getInstance() }.isSuccess
It works because if WebView is not available then CookieManager.getInstance() will throw AndroidRuntimeException.
getPackageManager().hasSystemFeature("android.software.webview") is not reliable. It will return true if the device is supposed to have a webview, but has disabled it in system settings.
I tested it on a Huawei P20 by going to Settings -> Apps -> Android System WebView -> Disable
There is a way around: CookieManager
AFAIK: There is no dedicated API that gives you that information. But if I told you there is a way around.
As Torkel mentioned, getPackageManager().hasSystemFeature(..) does not guarantee to tell the Android System WebView is enabled or disabled.
getPackageManager().hasSystemFeature(..) will still return true even a user disables the WebView in Settings.
CookieManager
So an alternative would be to use CookieManager.
An application WebView cookie is managed by CookieManager. It throws an exception if you try to create CookieManager instance when the WebView is disabled.
In Kotlin, you can get result from block statements but it is not possible in Java. So you can use Exception Handling logic in a function to decide.
Here is an example
public boolean webViewEnabled(){
try{
CookieManager.getInstance();
return true;
}catch(Exception e){
return false;
}
}
Note:
The current implementation might change in the future, or they might come up with a dedicated API for this. I have tried finding a perfect solution in many forums but this is the closest I have reached.
This solution will prevent your app from crashing but you will still see the error in your logs because CookieManager.getInstance throws an Exception object.
You can read more detail here https://source.android.com/compatibility/android-cdd.pdf.
According to the section WebView Compatibility, android.software.webview feature is indicate that device or oem must provides a complete implementation of the android.webkit.WebView API. So the number of device on play store was drop because actually, most device did not fully implemented all api required by webkit.
So if your web content required certain html5 feature please check here http://mobilehtml5.org/ if your app target kitkat or even lollipop+ then you should be safe.
So there is no need to declare android.software.webview feature unless your web content really need all api of webkit.
As of API 26, you can use WebView.getCurrentWebViewPackage() to check if there is a valid WebView installed and active. The CookieManager check can be really slow (upwards of 500ms) and can impact startup performance if the check is happening in the critical path.
From the documentation:
If WebView has already been loaded into the current process this method will return the package that was used to load it. Otherwise, the package that would be used if the WebView was loaded right now will be returned; this does not cause WebView to be loaded, so this information may become outdated at any time.
The WebView package changes either when the current WebView package is updated, disabled, or uninstalled. It can also be changed through a Developer Setting. If the WebView package changes, any app process that has loaded WebView will be killed. The next time the app starts and loads WebView it will use the new WebView package instead.
#return the current WebView package, or {#code null} if there is none.
You can verify that this is working as expected by swapping the WebView implementation and then calling this method to list the PackageName (e.g if you're running the dev/beta Chrome WebViews) by calling:
WebView.getCurrentWebViewPackage()?.packageName
You can test disabled state by running the following ADB command (although you might need an emulator running as root via adb root): adb shell pm disable <webview-package-id>.
WebView has been had in Android API 1 so it's always available what's not it's the internet connection
https://developer.android.com/reference/android/webkit/WebView.html
In you manifest you need internet permission :
<uses-permission android:name="android.permission.INTERNET" />
I'm using android Log.isLoggable api in order to determine whether my custom log tag is on and should I log. (I'm setting property using setprop log.tag. on the device).
As far as Docs says, and as I'm familiar with older version, it should return true if the log level in the property is equal or high than the one I'm checking in my code.
This works fine for Lollipop and below (api 22) it's seems that something was changed in Marshmallow, as I encounter inconsistency and buggy behavior, playing with the values of the tag will result sometimes with the wrong value returned from isLogabble(), for instance a concrete scenario I did a check in code :
boolean shouldLog = Log.isLoggable("mytag", Log.DEBUG);
Log.d("debug", "shouldLog = " + shouldLog);
I set log.tag.mytag value to some arbitrary string
launch my app, and see the isLoggable = false, that's OK
then I change log.tag.mytag to VERBOSE
launch my app, isLoggable still false , that's not OK! should be true now
same scenario is not reproducible at Lollipop and didn't encounter any other misbehaviors.
Any suggestions, do I miss something here?
I've been running into an issue with the MediaPlayer on Lollipop devices. Basically when the device screen is off (i.e. user locked the device) the playback continues, but ends about 1 - 2 seconds too early. This doesn't happen when the screen is on though.
I have an onCompletionListener on the MediaPlayer:
#Override
public void onCompletion(final MediaPlayer mediaPlayer) {
int progress = mediaPlayer.getCurrentPosition();
int duration = mediaPlayer.getDuration();
Log.d("PlaybackController", "progress: " + progress + " duration: " + duration);
Log.d("PlaybackController", "Delay: " + (duration - progress)); // I'm seeing a difference of 1 - 3 seconds :(.
mServiceHandler.postDelayed(new Runnable() {
#Override
public void run() {
broadcastCompleted();
}
}, Math.max(duration - progress, 0));
}
This usually prints: Delay: [1500 - 3000]. I was wondering if there was a wake lock I'm missing, but I'm making the correct locks mentioned here: http://developer.android.com/guide/topics/media/mediaplayer.html, which include a PARTIAL_WAKE_LOCK and a WifiLock. Is there something else I'm missing?
Ok it looks like the issue is Android 5.0.1's experimental MediaPlayer called NuPlayer. NuPlayer is being enabled by default on all Android 5.0.1 devices and is only disabled through Developer Options. I've filed a bug against Android here: https://code.google.com/p/android/issues/detail?id=94069&thanks=94069&ts=1420659450
Here's a sample email you can send your users when they face issues with Media playback on Android 5.0.1 devices:
It looks like this might be a bug on Android's new experimental MediaPlayer called NuPlayer. To fix this, please follow these steps:
Go to Android Settings
Go to "About Phone"
Scroll down to "Build Number" and tap the Build number 7 times.
You'll see a message saying "you are now X steps away from being a developer".
After tapping it 7 times it will say "You are now a developer!"
Go back to the main settings screen and you'll see a new option called "Developer Options" right above "About Phone"
Go into Developer Options and Unselect "Use NuPlayer (experimental)" under the Media section.
Update:
Setting a partial wake lock on the MediaPlayer resolves this problem:
playerToPrepare.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
A partial wake lock shouldn't have too big of an impact, and it seems like MediaPlayer itself cleans this up when playback completes.
-- Original Answer ---
I'm cross-posting my answer from here Prevent my audio app using NuPlayer on Android Lollipop 5.x?, until there's a fix out for NuPlayer, the best you can do is to detect when NuPlayer is enabled and take the user to the Developer Settings.
This approach checks Android's system properties values to see if the user have enabled the use of AwesomePlayer or not under Developer Settings. Since Lollipop have NuPlayer on by default, if this value is disabled, we know NuPlayer will be used.
Drop SystemProperties.java into your project for access to read the system properties, do not change its package name from android.os (it calls through to its corresponding JNI methods, so needs to stay the same).
You can now check if the phone is Lollipop/5.0, if AwesomePlayer is enabled, and act accordingly if it's not (e.g. by opening the Developer Settings):
public void openDeveloperSettingsIfAwesomePlayerNotActivated(final Context context) {
final boolean useAwesome = SystemProperties.getBoolean("persist.sys.media.use-awesome", false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !useAwesome) {
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
context.startActivity(intent);
}
}
I am building an app that requires a lot of drawing on the canvas. I notice that the app is a bit laggy in devices with high resolution (nexus 7 for example). I saw there is a Force GPU option in the developer option. When Force GPU is enabled, my app runs absolutely smooth.
I have read that this Force GPU option is called Hardware Acceleration and it is available only for Android 3.0 and above.
My app is targeting Android 2.3 and above.
Is it possible to programmatically enable Hardware Accelerated (or Force GPU--whatever the magic is called) on any Android 3.0 or above devices?
Something like:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
Turn On Hardware Accelerate HERE but How can i do this?
any code snippet would be welcome/helpful/thanks
}
I assume you've already added android:hardwareAccelerated to your Manifest file?
<application android:hardwareAccelerated="true" ...>
That is what enables hardware acceleration within your application per the guide on hardware acceleration and should do exactly what forcing GPU does at a system level.
Set minSdkVersion to 10 and targetSdkVersion to maximum
Like below
<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="17" />
then
<application android:hardwareAccelerated="true" ...>
Now will work
And for particularities
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB){
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
or
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB){
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
If you want to build your application using lower api level you can access the method via reflection:
try {
Method setLayerType = view.getClass().getMethod(
"setLayerType", new Class[] { int.class, Paint.class });
if (setLayerType != null)
setLayerType.invoke(view, new Object[] { LAYER_TYPE_X, null });
} catch (NoSuchMethodException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
Where LAYER_TYPE_X is the constant integer value of wanted layer type:
LAYER_TYPE_NONE = 0
LAYER_TYPE_SOFTWARE = 1
LAYER_TYPE_HARDWARE = 2
Is it possible to consistently detect if an Activity has hardware acceleration enabled when it is created? I'm worried that users of my library will enable it through the manifest when they shouldn't, by not specifically disabling it for my Activity (as I instruct them to do.)
The only solid information I can find (http://android-developers.blogspot.com/2011/03/android-30-hardware-acceleration.html) says that I can query View.isHardwareAccelerated() and Canvas.isHardwareAccelerated(). However, for my purposes, I would like to ensure it is off when my library's Activity is shown. So far, I can't get anything to report a consistent yes/no when it is on or off. I tried hacking in a dummy view, setting it to my activity and then testing it, but it always returns false. Also, I tried testing Window.getAttributes( ).flags, but they aren't showing it either.
I am testing this because the hardware accelerated draw path for my library doesn't function correctly, and there doesn't seem like there is any way to fix it.
Try FLAG_HARDWARE_ACCELERATED in flags in ActivityInfo for the activity, which you would get from PackageManager via getActivityInfo().
I'm new in Android so I was stuck even with the clues given in the answer above.. went to search around and found this code somewhere in the sea of Google. Hope it helps someone.
/**
* Returns true if the given Activity has hardware acceleration enabled
* in its manifest, or in its foreground window.
*
* TODO(husky): Remove when initialize() is refactored (see TODO there)
* TODO(dtrainor) This is still used by other classes. Make sure to pull some version of this
* out before removing it.
*/
public static boolean hasHardwareAcceleration(Activity activity) {
// Has HW acceleration been enabled manually in the current window?
Window window = activity.getWindow();
if (window != null) {
if ((window.getAttributes().flags
& WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0) {
return true;
}
}
// Has HW acceleration been enabled in the manifest?
try {
ActivityInfo info = activity.getPackageManager().getActivityInfo(
activity.getComponentName(), 0);
if ((info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("Chrome", "getActivityInfo(self) should not fail");
}
return false;
}