I want to create a log file for my Android app. I need this to debug the random crashes of the applications.
I want to create a function which always get called if there is a unhandelled exception. In the log I want the exception message. Is there any event kind mechanism which will get invoked on unhandelled exception in ANdroid?
try to use android Android Acra
Really good thing Please try this.
You can write your own log cat,
when your application is installed on real android device and not connected to Eclipse to get debug details..
Please check Tutorial :Read & Store Log-cat Programmatically in Android
also you may like to check this stack-overflow page I have posted solution code snip and related useful information.
You can use UncaughtExceptionHandler as shown below:
public class UncaughtExceptionHandler implements java.lang.Thread.UncaughtExceptionHandler {
String Tag;
Context myContext;
public UncaughtExceptionHandler(Context context, String TAG) {
Tag = TAG;
myContext = context;
}
public void uncaughtException(Thread thread, Throwable exception) {
StringWriter sw = new StringWriter();
exception.printStackTrace(new PrintWriter(sw));
Logger.appendErrorLog(sw.toString(), Tag);
Intent intent = new Intent(myContext, ForceClose.class);
intent.putExtra(BugReportActivity.STACKTRACE, sw.toString());
myContext.startActivity(intent);
Process.killProcess(Process.myPid());
System.exit(10);
}
}
Call this class in the first activity's onCreate() method:
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(getApplicationContext(),"FirstActivity"));
Related
I have a WebView in the layout xml of my MainActivity, to which I setWebViewClient(new WebViewClient()), followed by loadUrl(...) in onCreate.
Most of the time the app runs fine and the Web content is displayed correctly.
But in some cases, opening the app causes a crash. I've noticed that it happens when the app scheduled a PendingIntent broadcast with AlarmManager, which triggers a Notification whose contentIntent is a PendingIntent.getActivity set to launch MainActivity.
But it happens only in the case when the user has removed the app from the stack of active apps in the meantime (Notification is visible, not yet clicked, and stack if apps cleared. So, app process probably stopped?).
Seemingly no other system modifications in between (in particular no app/system update, no playing around with user profiles or Chrome app.)
Stack trace:
java.lang.RuntimeException:
at android.webkit.WebViewDelegate.getPackageId (WebViewDelegate.java:164)
at yj.a (PG:16)
at xH.run (PG:14)
at java.lang.Thread.run (Thread.java:764)
Occurs with Android 7.0 thru 9. Also, seems to have started to occur when I upgraded target SDK to 28.
I don't use explicitly a WebViewDelegate. It must be internal system code (hence the obfuscation).
By reading the source code of AOSP, it seems that the WebView fails to retrieve the package to which it belongs -- but why sometimes only!?
Any help appreciated! Thanks.
It has taken weeks of investigation on and off, but I've finally found why I'm seeing this issue. For me, it was just because I'd overridden the getResources() method in my application scope to use the current activity. Something like this:
public class MyApplication extends MultiDexApplication {
private static MyApplication sInstance = null;
private WeakReference<Activity> mCurrentActivity;
public static MyApplication getInstance() {
return sInstance;
}
public void setCurrentActivity(Activity activity) {
mCurrentActivity = new WeakReference<>(activity);
}
public Activity getCurrentActivity() {
return mCurrentActivity == null ? null : mCurrentActivity.get();
}
#Override
public Resources getResources() {
// This is a very BAD thing to do
Activity activity = getCurrentActivity();
if (activity != null) {
return activity.getResources();
}
return super.getResources();
}
}
This was done as a shortcut as I often wanted to get strings that were activity-specific, so I was calling MyApplication.getInstance().getResources().getString(). I now know this was a bad thing to do - removing my override of this method instantly fixed it.
So the key takeaway from this for me is that when the WebView is initialising, it MUST be able to get hold of the application context, so that the resources passed into WebViewDelegate.getPackageId() are at the application level - the activity context isn't enough, and causes this error.
As a side note - I wasn't even trying to add a WebView to my application. I was only actually using the following:
String userAgent = WebSettings.getDefaultUserAgent(this);
I was then passing this value into a custom media player that I'm using. Passing "this" as either application or activity scope always failed, due to my override.
Looking through documentation,you can see that error is thrown when package can't be found.Check your syntax ,package name and try again.
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/webkit/WebViewDelegate.java (Line 164)
/**
* Returns the package id of the given {#code packageName}.
*/
public int getPackageId(Resources resources, String packageName) {
SparseArray<String> packageIdentifiers =
resources.getAssets().getAssignedPackageIdentifiers();
for (int i = 0; i < packageIdentifiers.size(); i++) {
final String name = packageIdentifiers.valueAt(i);
if (packageName.equals(name)) {
return packageIdentifiers.keyAt(i);
}
}
throw new RuntimeException("Package not found: " + packageName);
}
If my Activity/Fragment requires that the coder provide a particular intent or argument, how should I handle the case where the coder did not provide this intent or argument?
Say I have the following Activity and Fragment:
public class MyActivity extends AppCompatActivity {
public static final String KEY = "KEY";
#Override
protected void onCreate(Bundle savedInstanceState) {
if (!getIntent().hasExtra(KEY)) {
throw new RuntimeException("KEY is required");
}
super.onCreate(savedInstanceState);
}
}
public class MyFragment extends Fragment {
public static final String KEY = "KEY";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (!getArguments().containsKey(KEY)) {
throw new RuntimeException("KEY is required");
}
//...
}
}
My gut tells me just to throw an Exception and force the app to shut down so the coder can rectify this problem in their code. If so then which Exception best describe this situation?
Furthermore, where is the best place to place such a code? For example, onCreat() for Activity? Or onAttach() or onCreateView() for Fragment?
You can always throw Exception, just make sure the describe the error in details to help other devs understand what they did wrong.
Also, Remember that it's always better to "fail early" than to postpone the Exception -
If there is a critical data you need, validate the data as soon as you can and don't "hide" the Exception under some button click of specific user flow - you want it to be clear as soon as someone opens the Activity to minimize the risk the dev won't notice his/hers error.
P.S
There is a nice pattern to minimize that kind of coding errors.
You can create a public static method to instantiate your Fragments and Activities.
Example:
public static Intent newIntent(Context context, String requiredStr, int requiredInt) {
Intent intent = new Intent(context, MyActivity.class);
intent.putExtra("extra_str", requiredStr);
intent.putExtra("extra_int", requiredInt);
return intent;
}
That way the dev doesn't have to remember all the required data.
And then you can start your Activity like so:
startActivity(MyActivity.newIntent(context, "string", 20));
It is totally reasonable to crash the app when it enters an invalid state due to programmer error. Fail fast and fail early. Of course you should include an error message that explains how the programmer can correct the error.
If a required value isn't present in an Intent or a Bundle, I would say the most natural exception to throw would be NullPointerException. You could also throw IllegalStateException.
This exception will occur only while developing application. If users get this exception then development is useless.
And while developing, to show developers some error in a organized way is nothing but to make it meaningful and understanding easily.
Though , here a simple toast or error log or alertdialog could be enough because end level users would never see this type of data passing exceptions!
To make app crash you can use UnsupportedOperationException because, here activity is changing (operation) without data (not supported).
Also you can use NullPointerException because some variable(which would be used in next step) is not getting value (null).
As the title suggests, I'm trying to create a class that will display an error message in android and my reasoning is that, instead of creating a pop up wherever it was needed as well as declaring the preset error strings in the MainActivity, it would be cleaner to create a separate class containing the preset info and the code necessary to actually display the message. The only parameter it would need would be the actual exception.
The way I tried to achieve this was by extending the AlertDialog.Builder class as follows:
public class showError extends AlertDialog.Builder
{
private enum exString
{
UnknownHostException, IOException,
ConnectException, Exception,
NoTimeTablesFoundException
}
private String UHE;
private String IOE;
private String CE;
private String NTTFE;
private String CNFE;
private String title;
private String E;
private String message ="";
private String exception="";
Context cxt;
exString enumExc;
public showError(Exception ex, Context cxt)
{
super(cxt);
this.cxt = cxt;
initString();
try
{
exception = ex.getClass().getName().toString();
exception = exception.substring(exception.lastIndexOf(".")+1);
enumExc = exString.valueOf(exception);
}
catch (IllegalArgumentException err)
{
enumExc = exString.valueOf("Exception");
}
setType();
setTitle(title);
setMessage(message);
setPositiveButton("OK", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
}
});
setIcon(android.R.drawable.ic_dialog_alert);
//show();
}
private void initString()
{
String UHE = cxt.getString(R.string.errorUHE);
String IOE = cxt.getString(R.string.errorIOE);
String CE = cxt.getString(R.string.errorCE);
String NTTFE = cxt.getString(R.string.errorNTTFE);
String title = cxt.getString(R.string.errorTitle);
String E = cxt.getString(R.string.errorUnknown);
}
private void setType()
{
switch (enumExc)
{
case UnknownHostException:
{ message = UHE; break; }
case IOException:
{ message = IOE; break; }
case ConnectException:
{ message = CE; break; }
case NoTimeTablesFoundException:
{ message = NTTFE; break; }
case Exception:
{ message = E + exception; break; }
}
}
}
The way I would like for this to work is:
new showError(whateverException, this);
However, for some reason I don't understand, it constantly gives a WindowLeaked exception?
I commented the show() command in the class and, instead, started using
new showError(whateverException, this).show();
and this seems to work sometimes but this also throws the WindowLeaked exception from time to time.
First of all, can someone shed some light as to why, since it is the same context, does it make a difference where I call the show command (in the constructor of the class itself, or by calling the .show() method)?
Why does calling show in the constructor always throw an exception, whereas calling it by using .show() of the instantiated class sometimes work?
I understand that this exception is thrown when the activity that creates the dialog ends before the dialog is dismissed. Is this correct?
And if it is, when I try using MainActivity as the context, am I supposed to believe that the MainActivity ends? How can it end while the app is still running?
ALso, if these dialogs are asynchronous (they are on a seperate thread from the UI thread so as to not block the UI thread, is my understanding correct?) how come they need to be dismissed before the activity ends?
Because, it seems, in order for my code to work, I would actually need to have the app wait for the dialog to be dismissed (sort of how you cannot use the main form of a Windows desktop app while a MessageBox is being displayed), but it is my understanding that android doesn't do that by design.
Even more confusing is if instead of using this approach, I create and show an AlertDialog.Builder in every catch block, that seems to work perfectly each time... why?
Second, I'm not exactly trying to find out how to fix the flaws of my design but what I actually am looking for is understanding why my design is flawed and what approach would be appropriate to achieve what I want?
Or better yet, what is the industry standard for handling exceptions and displaying the appropriate error messages to the user so that he knows what has gone wrong (whenever possible)?
Thank you for your time and excuse the lengthy read.
I'm a little confused about crash tracking with Analytics. AFIK it is only possible to store in Analytics strings which are no longer then 200bytes (per property). So it is not possible to see full stack traces, since they are commonly much longer than 200 chars.
However with the Analytics v2 API this is possible with the EasyTracker, as described here. There are also some half-duplicated which use the ga_reportUncaughtExceptions property like this one: Google Analytics crash report only shows first line of stack trace or Exception stack trace lost in Google Analytics v2 for Android?
Since I use the Tag-Manager I cannot use those solutions, I already figured out how I can correctly track the crash for the first 200bytes with the Tag-Manager, but what is about the full stack trace?
I also use ACRA to provide the user a way to contact us directly (with the stack trace as attachment), so the direct connection to the Google crash reporter is stopped and the Google Play Console shows no crashes.
It is possible to send full stack trace in Google Analytics v4 for all the uncaught exceptions in Android.
Set the automatic activity tracking to true in your Application class.
// Enable automatic activity tracking for your app
tracker.enableAutoActivityTracking(true);
Further override the getDescription method of the StandardExceptionParser class. The Throwable contains array of stack trace elements which if converted to string format will be your stack trace as we see in Logs.
public class AnalyticsExceptionParser extends StandardExceptionParser {
public AnalyticsExceptionParser(Context context, Collection<String> additionalPackages) {
super(context, additionalPackages);
}
#Override
protected String getDescription(Throwable cause,
StackTraceElement element, String threadName) {
StringBuilder descriptionBuilder = new StringBuilder();
final Writer writer = new StringWriter();
final PrintWriter printWriter = new PrintWriter(writer);
cause.printStackTrace(printWriter);
descriptionBuilder.append(writer.toString());
printWriter.close();
return descriptionBuilder.toString();
}
}
Finally, provide your AnalyticsExceptionParser to an instance of ExceptionReporter.
public class MyApplication extends Application {
#Override
public void onCreate() {
...
ExceptionReporter reporter = new ExceptionReporter(getTracker(), Thread.getDefaultUncaughtExceptionHandler(), this);
reporter.setExceptionParser(new AnalyticsExceptionParser(this, packages));
Thread.setDefaultUncaughtExceptionHandler(reporter);
}
}
Thats it. See your crash reports on your GA console in Behavior > Crashes and Exceptions.
You can override the ExceptionParser that is used by the main thread to tweak it to report full stacktrace:
final Thread.UncaughtExceptionHandler uncaughtExceptionHandler =
Thread.getDefaultUncaughtExceptionHandler();
if (uncaughtExceptionHandler instanceof ExceptionReporter) {
ExceptionReporter exceptionReporter = (ExceptionReporter) uncaughtExceptionHandler;
exceptionReporter.setExceptionParser(new ExceptionParser() {
#Override
public String getDescription(String s, Throwable throwable) {
return "Thread: " + s + ", Stacktrace: " + ExceptionUtils.getStackTrace(throwable);
}
});
}
Put this where you instantiate your Google Analytics instance. The ExceptionUtils class I use is from apache's common-lang3 library.
CustomExceptionHandler
public class CustomExceptionHandler implements UncaughtExceptionHandler {
private Context ctx;
private ContentResolver cr;
public CustomExceptionHandler(Context ctx, ContentResolver cr) {
this.ctx = ctx;
this.cr = cr;
}
public void uncaughtException(Thread t, Throwable e) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
String stacktrace = result.toString();
printWriter.close();
String deviceUuid = Utilities.DeviceUuid(ctx, cr);
String bluetoothName = Utilities.LocalBluetoothName();
AsyncTasks.ErrorLogTask logTask = new AsyncTasks.ErrorLogTask(e, bluetoothName, deviceUuid, stacktrace);
logTask.execute();
}
}
Called from my main activity:
Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(getBaseContext(), getContentResolver()));
When an exception occurs now, i dont get the regular "unfortunately, has stopped" popup. Its just a black screen. If i remove my call from my main activity, in other words dont use the CustomExceptionHandler anymore, i get the default behaviour.
Is there any way to implement the default error behaviour in my class?
Thanks in advance!
You can add the following at the end of your exception handler to get the "unfortunately has stopped" dialog:
System.exit(1);
However, this will cause the process to terminate which means that your AsyncTask will not run to completion.
In any case, I would doubt that your code will run reliably anyway if you are in an uncaughtExceptionHandler, because you have no idea what the state of your application is. It might work, and it might not. What you could also try is to create a new Thread in your uncaughtExceptionHandler and have that thread sleep for a little while and then terminate the application using System.exit(). That may give your AsyncTask enough time to run to completion.