I recieve from google play msg that my app crash, in the msg
java.lang.NullPointerException
at android.webkit.WebViewDatabase.initDatabase(WebViewDatabase.java:234)
at android.webkit.WebViewDatabase.init(WebViewDatabase.java:212)
at android.webkit.WebViewDatabase.access$000(WebViewDatabase.java:40)
at android.webkit.WebViewDatabase$1.run(WebViewDatabase.java:193)
I don't find in google or in stackoverflow similar probloem so I dont know why this crach, but I know that cause by webview.
Looks to be the same as this issue here (plus potential workaround):
https://code.google.com/p/android/issues/detail?id=35204
I found the code for WebViewDatabase here (it's not exactly the same version, but there is enough context to get the picture):
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/webkit/WebViewDatabase.java#WebViewDatabase.initDatabase%28android.content.Context%29
If you look at the code for initDatabase(), there is a potential NPE on the line that I marked with "****". Note that the following line checks for NULL, so it looks to be a bit dumb:
private void initDatabase(Context context) {
try {
mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
} catch (SQLiteException e) {
// try again by deleting the old db and create a new one
if (context.deleteDatabase(DATABASE_FILE)) {
mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
null);
}
}
mDatabase.enableWriteAheadLogging(); ****
// mDatabase should not be null,
// the only case is RequestAPI test has problem to create db
if (mDatabase == null) {
mInitialized = true;
notify();
return;
Try checking any code that calls android.webkit.WebViewDatabase.initDatabase()
Does it have error handling or any problem with opening the database in certain situations?
Do you have error handling / object detection? Can you show a few lines of the code?
I have inspected provided links, android source codes, internal crash analytics and it seems that problem exists only on android 4.0 - 4.0.4. I have tested my suggestion on android 2.3.6, 4.0.3, 4.4.2 and it seems to be correct. So i finished with following workaround for this issue:
package com.android.example;
import android.content.Context;
import android.os.Build;
public class WebViewUtil {
private static final String WEBVIEW_DATABASE_FILE = "webview.db";
public static boolean isWebViewCorrupted(Context context) {
try {
int currentSdk = Build.VERSION.SDK_INT;
if (currentSdk == Build.VERSION_CODES.ICE_CREAM_SANDWICH
|| currentSdk == Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
try {
context.openOrCreateDatabase(WEBVIEW_DATABASE_FILE, 0, null);
} catch (Throwable t) {
// try again by deleting the old db and create a new one
context.deleteDatabase(WEBVIEW_DATABASE_FILE);
context.openOrCreateDatabase(WEBVIEW_DATABASE_FILE, 0, null);
}
}
return false;
} catch (Throwable t) {
}
return true;
}
}
Related
I am developing a small game for Air Android in flash CS6, this game I'm programming object-oriented with classes written in AS3.
I want you to help me write a class that connects to SQLite from scratch, please!
I googled but only there information to FLEX or code for insert into frames (and i get lost because i don't know that libraries to import), it's not what I want.
I want to create the connection class to use in my class Main.
I hope I have your support, thank you.
Excuse my English but I speak Spanish.
You need 'Synchronous' or 'Asynchronous' sqlite connection ? You can see differences here: - Best practices for developing AIR Application with SQLite
1. There is an example for 'async' connection :SQLConnection - you just have to modify and remove transactions part (if you do not need it).
2. 'sync' is easy and i prefer this way
import flash.data.SQLConnection;
import flash.filesystem.File;
import flash.data.SQLMode; //if you use SQLMode: CREATE | UPDATE | READ
import flash.data.SQLStatement;
import flash.data.SQLResult;
import flash.errors.SQLError;
public class SQLiteConn
{
private var dbFile:File = new File("pathtofile");
private var conn:SQLConnection;
private var stmt:SQLStatement;
private var arr:Array;
public function SQLiteConn()
{
}
//open sqlite DB
public function openSQLite():Boolean {
conn = new SQLConnection;
if (dbFile.exists) {
try {
conn.open(dbFile);
return true;
}
catch (error:SQLError) {
trace(error.details, error.message);
}
}
return false;
}
//execute statement and get result/s
public function executeStatement(stmtText:String, param1:String, param2:String):Array {
if (conn.connected) {
stmt = new SQLStatement();
stmt.sqlConnection = con;
stmt.text = stmtText;
stmt.parameters[0] = param1;
stmt.parameters[1] = param2;
try {
stmt.execute();
var result:SQLResult = stmt.getResult();
if (result.data != null) {
var total:int = result.data.length;
for (var i:int = 0; i < total; i++) {
row = result.data[i];
arr.push( { id:row.tablerowID } );
}
}
else {
arr.push({id: -1}); //no result/s
}
}
catch (error:SQLError) {
arr.push({id: -2}); //sqlite error
}
}
else {
arr.push({id: -2}); //no connection
}
return arr;
}
//close sqliteConnection
public function closeSQLiteConn():void {
if (conn.connected) {
conn.close();
}
}
}
you can also 'dispatch event' from that class - it is your choise :)
Is there a way to programmatically configure an Android application to filter the log messages sent to logcat? I do understand, that logcat can be configured to filter out stuff, but I want to do the same inside the Android application.
Use case - I am actually using robolectric for my test cases, that I can run directly on my host machine and not on an emulator. This is actually extremely convenient for non-GUI stuff. Some of my code emits Android logs. I cannot attach logcat to see the output of that. I can redirect logs to regular stdout. But at this point I don't have filtering, so it's either grep or similar or sieving through thousands of lines of irrelevant stuff.
That's what I did:
public class CustomPrintStream extends PrintStream {
private String regexFilter;
private Pattern pattern;
public IonPrintStream(#NonNull File file) throws FileNotFoundException {
super(file);
}
public String getRegexFilter() {
return regexFilter;
}
public void setRegexFilter(String regexFilter) {
this.regexFilter = regexFilter;
if (regexFilter != null && !regexFilter.isEmpty()) {
pattern = Pattern.compile(regexFilter);
} else {
pattern = null;
}
}
#Override
public void println(String x) {
if (x != null && pattern != null && !pattern.matcher(x).find()) {
return;
}
System.out.println(x);
}
}
Usage on Robolectric (you can do it on your #Before method):
File file = new File("log.txt");
if (!file.exists() && !file.createNewFile()) {
throw new RuntimeException("Log file could not be created");
}
CustomPrintStream printStream = new CustomPrintStream(file);
printStream.setRegexFilter("[your regex here]");
ShadowLog.stream = printStream;
In your case, as you don't want to show some logs, you could filter something like this:
//I don't want to log CursorWindowStats and SQLiteCursor tags:
printStream.setRegexFilter("^((?!CursorWindowStats|SQLiteCursor).)*$");
I want to receive notification regarding crash report due to uncaught exception in my google analytics for my android app. I followed the steps given in https://developers.google.com/analytics/devguides/collection/android/v4/exceptions#parsing
but still I dont receive any crash report. I had a runtime exception when my app runs. I added the code for ga_reportUncaughtException as true:
true
in my analytics.xml. Is there anything else I need to add in order to get hit in google analytics account. Please help!
There is an open issue in Analytics. I'm experiencing the same behavior but on real devices from API 10 to 19.
https://code.google.com/p/analytics-issues/issues/detail?id=443
EDIT1: Removed question, just to answer the question described.
EDIT2: I tried to capture and send the exceptions using the Analytics ExceptionBuilder, but it didn't work.
It looks like the report is sent (at least LogCat is showing that the crash is reported), but it is not processed by Analytics.
While Google replies to the issue, I'm using this workaround. I guess it is not the best solution and the code can be improved, but it works for me:
I created a custom dimension in Analytics following this steps https://support.google.com/analytics/answer/2709829?hl=en
In my App, I created a custom exception handler, using the Analytics ExceptionReporter class. When an exception is caught, I get the stack trace and truncate it to 150 Bytes (Actually I'm getting only the first line of the stack and truncate it to 150 chars. I'm assuming that 1Char = 1 Byte). I have to truncate it, because it is the Max Lenght allowed by Analytics when sending custom dimensions values.
The stack trace is stored in a Shared Preference instead of being sent. I tried to send it directly, but it does not work once the App has crashed.
package com.company.package;
import java.lang.Thread.UncaughtExceptionHandler;
import android.content.Context;
import com.google.android.gms.analytics.ExceptionParser;
import com.google.android.gms.analytics.ExceptionReporter;
import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
public class GoogleAnalyticsTracker {
private static Tracker mTracker;
private static GoogleAnalytics mGa;
private Context mContext;
public GoogleAnalyticsTracker(Context context, int resource) {
mContext = context;
mGa = GoogleAnalytics.getInstance(context);
mTracker = getTracker(resource);
Thread.setDefaultUncaughtExceptionHandler(new AnalyticsExceptionReporter(mTracker,
Thread.getDefaultUncaughtExceptionHandler(), context));
}
synchronized Tracker getTracker(int xmlResource) {
return mGa.newTracker(xmlResource);
}
public void sendScreenLabel(String screenLabel) {
mTracker.setScreenName(screenLabel);
mTracker.send(new HitBuilders.AppViewBuilder().build());
}
public void sendCustomDimension(int index, String value) {
mTracker.send(new HitBuilders.AppViewBuilder().setCustomDimension(index, value).build());
}
private class AnalyticsExceptionReporter extends ExceptionReporter {
public AnalyticsExceptionReporter(Tracker tracker, UncaughtExceptionHandler originalHandler, Context context) {
super(tracker, originalHandler, context);
setExceptionParser(new AnalyticsExceptionParser());
}
#Override
public void uncaughtException(Thread t, Throwable e) {
String exceptionDescription = getExceptionParser().getDescription(t.getName(), e);
//Add code to store the exception stack trace in shared preferences
super.uncaughtException(t, e);
}
}
private class AnalyticsExceptionParser implements ExceptionParser {
#Override
public String getDescription(String arg0, Throwable arg1) {
StringBuilder exceptionFirsLine = new StringBuilder();
for (StackTraceElement element : arg1.getStackTrace()) {
exceptionFirsLine.append(element.toString());
break;
}
//150 Bytes is the maximum allowed by Analytics for custom dimensions values. Assumed that 1 Byte = 1 Character (UTF-8)
String exceptionDescription = exceptionFirsLine.toString();
if(exceptionDescription.length() > 150)
exceptionDescription = exceptionDescription.substring(0, 149);
return exceptionDescription;
}
}
}
In the MainActivity when OnStart(), I check if there is any stored stack trace in the shared preferences. If so, I send the custom dimension and clear the shared preference.
#Override
protected void onStart() {
super.onStart();
String exception = getExceptionFromSharedPreferences(this);
if(exception != null && !exception.isEmpty()) {
MainApplication.googleAnalyticsTracker.sendCustomDimension(1, exception);
}
clearExceptionFromSharedPreferences(this);
}
Finally I created a custom report in Analytics
EDIT 3:
I realized that I was sending only the fileName and lineNumber, but not the ExceptionName and the origin of the Exception in my package. I have improved the answer by adding code to also send that info.
private class AnalyticsExceptionParser implements ExceptionParser {
#Override
public String getDescription(String arg0, Throwable arg1) {
String exceptionDescription = getExceptionInfo(arg1, "", true) + getCauseExceptionInfo(arg1.getCause());
//150 Bytes is the maximum allowed by Analytics for custom dimensions values. Assumed that 1 Byte = 1 Character (UTF-8)
if(exceptionDescription.length() > 150)
exceptionDescription = exceptionDescription.substring(0, 150);
return exceptionDescription;
}
}
//#endregion
//#region PRIVATE METHODS
private String getCauseExceptionInfo(Throwable t) {
String causeDescription = "";
while(t != null && causeDescription.isEmpty()) {
causeDescription = getExceptionInfo(t, "com.myPackageName", false);
t = t.getCause();
}
return causeDescription;
}
private String getExceptionInfo(Throwable t, String packageName, boolean includeExceptionName) {
String exceptionName = "";
String fileName = "";
String lineNumber = "";
for (StackTraceElement element : t.getStackTrace()) {
String className = element.getClassName().toString().toLowerCase();
if(packageName.isEmpty() || (!packageName.isEmpty() && className.contains(packageName))){
exceptionName = includeExceptionName ? t.toString() : "";
fileName = element.getFileName();
lineNumber = String.valueOf(element.getLineNumber());
return exceptionName + "#" + fileName + ":" + lineNumber;
}
}
return "";
}
From my experience you need to understand two things about crashes and exceptions in Google Analytics:
1) Only basic information is stored - Google Analytics will only save the name of the exception and the location (Code file and line number) where the exception was thrown. No information beyond that will be accessible to you on GA. This is definitely not ideal and if you wish to track the actual content of your exceptions (mainly the call stack), use Google Play or implement your own solution.
2) Exceptions are not real-time. Exception information is collected and updated maybe once a day, so if you're experimenting with exceptions and you don't see them immediately, just give it time.
I need to uniquely identify an Android device. I read about ANDROID_ID but it seems to have problems with Android 2.2. Then there are other identifiers related to TelephonyManager, but I reckon they don't work on tablets.
So, looking for something working on every device I stumbled upon the GSF ID KEY (google service framework id). Do you guys think this is a reliable and always working solution?
This is the code I found to retrieve the GSF ID KEY:
private static String getGsfAndroidId(Context context)
{
Uri URI = Uri.parse("content://com.google.android.gsf.gservices");
String ID_KEY = "android_id";
String params[] = {ID_KEY};
Cursor c = context.getContentResolver().query(URI, null, null, params, null);
if (!c.moveToFirst() || c.getColumnCount() < 2)
return null;
try
{
return Long.toHexString(Long.parseLong(c.getString(1)));
}
catch (NumberFormatException e)
{
return null;
}
}
In case someone is wondering if this method works the answer is yes, I tried it (and used it in an app I put on the Android market with thousands of downloads) and it works. Note: the GSF ID KEY changes every time the user does a factory reset or messes up with Google Services, but it was good enough for my purpose.
using Android Studio, I get auto-recommendations from lint. here is the code, after revised. it may solve exceptions reported by https://stackoverflow.com/users/423171/cprcrack
private static String getGsfAndroidId(Context context)
{
Uri URI = Uri.parse("content://com.google.android.gsf.gservices");
String ID_KEY = "android_id";
String params[] = {ID_KEY};
Cursor c = context.getContentResolver().query(URI, null, null, params, null);
if (c != null && (!c.moveToFirst() || c.getColumnCount() < 2)){
if(!c.isClosed())
c.close();
return null;
}
try {
if (c != null) {
String result = Long.toHexString(Long.parseLong(c.getString(1)));
if(!c.isClosed())
c.close();
return result;
}else {
return null;
}
} catch (NumberFormatException e) {
if(!c.isClosed())
c.close();
return null;
}
}
Can't talk about production tests, but I noticed that in my Nexus 5 with Android 5.0, I had to add the following permission: com.google.android.providers.gsf.permission.READ_GSERVICES. Otherwise an exception is raised when using your code.
Can't talk about production tests, but I noticed that in my Nexus 5 with Android 5.0, I had to add the following permission: com.google.android.providers.gsf.permission.READ_GSERVICES. Otherwise an exception is raised when using your code.
Share Improve this answ
Google added a new ART runtime with Android 4.4. How can I determine whether ART or Dalvik is the current runtime?
Update
At least, as early as June 2014 Google has released an official documentation on how to correctly verify the current runtime in use:
You can verify which runtime is in use by calling System.getProperty("java.vm.version"). If ART is in use, the property's value is "2.0.0" or higher.
With that, now there is no need to go through reflection and simply check the corresponding system property:
private boolean getIsArtInUse() {
final String vmVersion = System.getProperty("java.vm.version");
return vmVersion != null && vmVersion.startsWith("2");
}
One possible way is to read the respective SystemProperty through reflection.
Sample:
package com.example.getcurrentruntimevalue;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MainActivity extends Activity {
private static final String SELECT_RUNTIME_PROPERTY = "persist.sys.dalvik.vm.lib";
private static final String LIB_DALVIK = "libdvm.so";
private static final String LIB_ART = "libart.so";
private static final String LIB_ART_D = "libartd.so";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView)findViewById(R.id.current_runtime_value);
tv.setText(getCurrentRuntimeValue());
}
private CharSequence getCurrentRuntimeValue() {
try {
Class<?> systemProperties = Class.forName("android.os.SystemProperties");
try {
Method get = systemProperties.getMethod("get",
String.class, String.class);
if (get == null) {
return "WTF?!";
}
try {
final String value = (String)get.invoke(
systemProperties, SELECT_RUNTIME_PROPERTY,
/* Assuming default is */"Dalvik");
if (LIB_DALVIK.equals(value)) {
return "Dalvik";
} else if (LIB_ART.equals(value)) {
return "ART";
} else if (LIB_ART_D.equals(value)) {
return "ART debug build";
}
return value;
} catch (IllegalAccessException e) {
return "IllegalAccessException";
} catch (IllegalArgumentException e) {
return "IllegalArgumentException";
} catch (InvocationTargetException e) {
return "InvocationTargetException";
}
} catch (NoSuchMethodException e) {
return "SystemProperties.get(String key, String def) method is not found";
}
} catch (ClassNotFoundException e) {
return "SystemProperties class is not found";
}
}
}
Hope this helps.
For anyone needing a JNI version:
#include <sys/system_properties.h>
static bool isArtEnabled() {
char buf[PROP_VALUE_MAX] = {};
__system_property_get("persist.sys.dalvik.vm.lib.2", buf);
// This allows libartd.so to be detected as well.
return strncmp("libart", buf, 6) == 0;
}
Or if you want to follow a code path closer to what shoe rat posted,
static bool isArtEnabled(JNIEnv *env)
{
// Per https://developer.android.com/guide/practices/verifying-apps-art.html
// if the result of System.getProperty("java.vm.version") starts with 2,
// ART is enabled.
jclass systemClass = env->FindClass("java/lang/System");
if (systemClass == NULL) {
LOGD("Could not find java.lang.System.");
return false;
}
jmethodID getProperty = env->GetStaticMethodID(systemClass,
"getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
if (getProperty == NULL) {
LOGD("Could not find java.lang.System.getProperty(String).");
return false;
}
jstring propertyName = env->NewStringUTF("java.vm.version");
jstring jversion = (jstring)env->CallStaticObjectMethod(
systemClass, getProperty, propertyName);
if (jversion == NULL) {
LOGD("java.lang.System.getProperty('java.vm.version') did not return a value.");
return false;
}
const char *version = env->GetStringUTFChars(jversion, JNI_FALSE);
// Lets flip that check around to better bullet proof us.
// Consider any version which starts with "1." to be Dalvik,
// and all others to be ART.
bool isArtEnabled = !(strlen(version) < 2 ||
strncmp("1.", version, 2) == 0);
LOGD("Is ART enabled? %d (%s)", isArtEnabled, version);
env->ReleaseStringUTFChars(jversion, version);
return isArtEnabled;
}
The Android docs actually give the following suggestion:
You can verify which runtime is in use by calling System.getProperty("java.vm.version"). If ART is in use, the property's value is "2.0.0" or higher.
This seems accurate on my Nexus 4 w/ ART enabled (running Android 4.4.4). Nexus 5 on Dalvik returned 1.6.0.
A simple solution :
String vm = System.getProperty("java.vm.name") + " " + System.getProperty("java.vm.version");
On my Android 8.0 (API 26) phone, it returns Dalvik 2.1.0 .
I think you should be able to use System.getProperty with java.vm.name as the key.
In the JavaDoc its value is Dalvik, which let's hope it is Art or ART when using that runtime. It's worth a try...
final String vm = VMRuntime.getRuntime().vmLibrary();
and then compare vm with "libdvm.so" or "libart.so" to check if it is Dalvik or ART.
Reference: https://gitorious.org/cyandreamproject/android_frameworks_base/commit/4c3f1e9e30948113b47068152027676172743eb1