Method addObserver must be called on the main thread - android

I started getting this illegal state exception recently.
This is the stack trace:
E/AndroidRuntime: FATAL EXCEPTION: UploaderServiceThread1640451978001
Process: com.sister.ivana4k, PID: 2032
java.lang.IllegalStateException: Method addObserver must be called on the main thread
at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:172)
at androidx.activity.ComponentActivity.(ComponentActivity.java:231)
at androidx.fragment.app.FragmentActivity.(FragmentActivity.java:127)
at androidx.appcompat.app.AppCompatActivity.(AppCompatActivity.java:87)
at com.sister.ivana.database.OrmLiteBaseAppCompatActivity.(OrmLiteBaseAppCompatActivity.java:33)
at com.sister.ivana.database.DBServices.VehicleServices.(VehicleServices.java:28)
at com.sister.ivana.upload.UploadType.getFileURL(UploadType.java:548)
at com.sister.ivana.upload.UploadType.createSpinItPicturesUploadDescription(UploadType.java:475)
at com.sister.ivana.upload.UploadType.getUploadRequest(UploadType.java:299)
at com.sister.ivana.upload.Uploader.startCompletionRequest(Uploader.java:704)
at com.sister.ivana.upload.Uploader.processQueue(Uploader.java:443)
at com.sister.ivana.upload.Uploader.handleMessage(Uploader.java:198)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.os.HandlerThread.run(HandlerThread.java:67)
I have searched around and found that I could potentially wrap the call to do it on the main thread, unfortunately from what I can see the call is made by an internal library so I am not sure how I could resolve it or at least mitigate it.
What can I do? Let me know if you need more info.
Vehicle Services class:
public class VehicleServices extends OrmLiteBaseAppCompatActivity {
private final String vinNumber;
private final Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public VehicleServices(String vinNumber, Context context) { //line 28
this.vinNumber = vinNumber;
this.context = context;
}
public Project getProject() {
try {
DatabaseHelper helper = getHelperInternal(this.context);
final Vehicle vehicle = helper.getVehicleDao().queryForId(this.vinNumber);
if (vehicle != null) {
Dao<Project, ?> dao = helper.getProjectDao();
for (Project project : dao) {
final String projectID = project.getProject().replace(",", "");
if (projectID.equals(vehicle.getProject())) {
return project;
}
}
}
} catch (SQLException ex) {
Util.logError("Database error adding new VIN", ex);
}
return null;
}
}
I have to also mention that I inherited this codebase a few days ago and I am aware that best practices were thrown out when building this. (Memory leaks and bugs everywhere, spaghetti code, you name it) I am looking for a way to fix this particular exception to the greatest degree possible.
I have access to all the files except android internal libraries.

Related

Does LeakCanary has callback?

How can I get the LeakCanary log or any kind data about the leak?
Does LeakCanary has any kind of Callback that we can use to get the "leak data" to do something with it in the time it's happend?
I want to send the data to my FireBase or some other DB.
I searched in the documentation, but didnt found somthing about it.
Thanks to all
TLDR; you need to extend DisplayLeakService
https://github.com/square/leakcanary/wiki/Customizing-LeakCanary#uploading-to-a-server
You can change the default behavior to upload the leak trace and heap dump to a server of your choosing.
Create your own AbstractAnalysisResultService. The easiest way is to extend DisplayLeakService in your debug sources:
public class LeakUploadService extends DisplayLeakService {
#Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
if (!result.leakFound || result.excludedLeak) {
return;
}
if (result.leakFound) {
uploadLeakToServer(result, leakInfo);
}
}
private void uploadLeakToServer(AnalysisResult result, String leakInfo) {
// TODO Upload result to server
}
}
You can translate leak traces to fake exceptions with AnalysisResult.leakTraceAsFakeException() and upload them to a crash reporting backend. Here's how you could do it with Bugsnag:
public class LeakUploadService extends DisplayLeakService {
#Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
if (!result.leakFound || result.excludedLeak) {
return;
}
if (result.leakFound) {
uploadLeakToServer(result, leakInfo);
}
}
private void uploadLeakToServer(AnalysisResult result, String leakInfo) {
Client bugsnagClient = new Client(getApplication(), "YOUR_BUGSNAG_API_KEY", false);
bugsnagClient.setSendThreads(false);
bugsnagClient.beforeNotify(error -> {
// Bugsnag does smart grouping of exceptions, which we don't want for leak traces.
// So instead we rely on the SHA-1 of the stacktrace, which has a low risk of collision.
String stackTraceString = Logs.getStackTraceString(error.getException());
String uniqueHash = Strings.createSHA1Hash(stackTraceString);
error.setGroupingHash(uniqueHash);
return true;
});
MetaData metadata = new MetaData();
metadata.addToTab("LeakInfo", "LeakInfo", leakInfo);
bugsnagClient.notifyBlocking(result.leakTraceAsFakeException(), Severity.ERROR, metadata);
}
}
Next, you need to specify the listener service class in LeakCanary:
public class DebugExampleApplication extends ExampleApplication {
#Override protected void installLeakCanary() {
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.listenerServiceClass(LeakUploadService.class);
.buildAndInstall();
}
}
Don't forget to register the service in your debug AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<application android:name="com.example.DebugExampleApplication">
<service android:name="com.example.LeakUploadService" />
</application>
</manifest>
My solution is a little bit different (based on the sample from square: https://square.github.io/leakcanary/recipes/#uploading-to-bugsnag), but the idea is the same. We use Sentry and Firebase via Timber to log memory leaks. I find sentry logging a bit more convenient since it shows the exact steps that a I took before getting a memory leak (screens opened, background/foreground).
/**
* Helper class to record leak canary memory leak traces on Timber.
*/
class LeakCanaryService : OnHeapAnalyzedListener {
private val defaultLeakListener = DefaultOnHeapAnalyzedListener.create()
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
// Delegate to default behavior (notification and saving result)
defaultLeakListener.onHeapAnalyzed(heapAnalysis)
when (heapAnalysis) {
is HeapAnalysisSuccess -> {
val allLeakTraces = heapAnalysis
.allLeaks
.toList()
.flatMap { leak ->
leak.leakTraces.map { leakTrace -> leak to leakTrace }
}
allLeakTraces.forEach { (leak, leakTrace: LeakTrace) ->
val exception = MemoryLeakReportingException(leak.shortDescription)
Timber.e(exception, "Memory leak recorded: ${exception.message}\n$leakTrace")
}
}
is HeapAnalysisFailure -> {
// Please file any reported failure to
// https://github.com/square/leakcanary/issues
Timber.e(
heapAnalysis.exception,
"Memory leak analysis failed: ${heapAnalysis.exception.message}"
)
}
}
}
class MemoryLeakReportingException(message: String) : RuntimeException(message)
}
You initialize this class in the App class:
LeakCanary.config = LeakCanary.config.copy(
onHeapAnalyzedListener = LeakCanaryService()
)

Is there a way to use a DexClassLoader without using reflection *everywhere*?

I have a library that I plan on using in dex form. I want to compile directly against this library, but not export it. Instead I want to drop it in my resources and use a class loader to actually instantiate it.
So here's my library:
public class Foo {
public doFoo(String message) {
}
public doFoo(int count, String message) {
}
}
Now I want to call doFoo(). A lot. More than it's probably reasonable to use reflection for. Right now it works with:
public class FooConsumer {
private final DexClassLoader fooLoader;
public FooConsumer(DexClassLoader fooLoader) {
this.fooLoader = fooLoader;
}
public void go() {
Class<?> fooClass = fooLoader.loadClass("com.library.Foo");
Object fooInstance = fooClass.newInstance();
Method fooMethodDoFoo = fooClass.getMethod("doFoo", String.class);
fooMethodDoFoo.invoke(fooInstance, "Hello World");
}
This is obviously fugly. Especially since I haven't included any of the exception handling, as there are half a dozen throwables to catch in there. I could cache a bunch of stuff, helping me with speed a bit, but not a lot.
Normally I'd have both aware of a third library that has an interface, but the library has some static methods and I can't edit it anyway. It'd be really nice if I could do something like:
public class FooConsumer {
private FooAccessor accessor;
public FooConsumer(DexClassLoader fooLoader) {
Object fooInstance = fooLoader.loadClass("com.library.Foo").newInstance();
Log.i("TEST", "fooInstance: " + fooInstance);
this.accessor = new FooAccessor(fooInstance);
}
public void go() {
accessor.doFoo("Hello World");
}
private static class FooAccessor {
private Foo fooInstance;
public FooAccessor(Object instance) {
fooInstance = (Foo)instance;
}
public void doFoo(String message) {
fooInstance.doFoo(message);
}
}
}
See what I did there? The inner class is just a wrapper around the Foo object, I've linked against it, but not exported it, and all is good in the world. But it doesn't work. In logcat I get
I/TEST: fooInstance: com.library.Foo#413b1b68
E/AndroidRuntime: java.lang.NoClassDefFoundError: com.library.Foo
...
Is there a way to have FooAccessor use the class loader I passed in? Or is the use of class loaders a damnation into reflection hell.
You might want to take a look at this gist.
https://gist.github.com/nickcaballero/7045993
It uses reflection to merge the new DexClassLoader to in-stock BaseDexClassLoader.

Why AsyncTask behaves differently if declared as inner class of an Activity or on a separate file?

I'm using an ORM for Android called Sugar to persist my models on the database and
I'm using it inside my AsyncTask.
Here is its declaration:
public class LoginTask extends AsyncTask<Object, Integer, String> {
private Context context;
private ProgressDialog progressDialog;
public LoginTask(Context context) {
this.context = context;
}
#Override
protected void onPreExecute() {
progressDialog = new ProgressDialog(context) {
{
setMessage("Authenticating...");
setTitle("Login");
setCancelable(false);
setIndeterminate(true);
show();
}
};
}
#Override
protected String doInBackground(Object... params) {
String email = (String) params[0];
String password = (String) params[1];
try {
User user = LoginWebService.loginUser(email, password,
context);
user.save();
} catch (CommunicationException e) {
e.printStackTrace();
return e.getMessage();
}
return null;
}
#Override
protected void onPostExecute(final String result) {
progressDialog.dismiss();
}
}
The line user.save() above, that saves the user model in the db, is the one that causes the exception. The strange thing is that if I declare the task above as an inner class from the activity, it works fine, but if I declare the task on a separate file, it throws this exception:
E/AndroidRuntime(17172): at com.app.task.LoginTask.doInBackground(LoginTask.java:47)
E/AndroidRuntime(17172): at com.app.task.LoginTask.doInBackground(LoginTask.java:1)
E/AndroidRuntime(17172): at android.os.AsyncTask$2.call(AsyncTask.java:264)
E/AndroidRuntime(17172): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
E/AndroidRuntime(17172): ... 5 more
E/AndroidRuntime(17172): Caused by: java.lang.RuntimeException: Cant create handler inside thread that has not called Looper.prepare()
I can't see what makes the difference as I can't see any sense on this.
You are trying to access/update the UI from a background thread. The exception that is thrown in the last sentence of log cat indicates "these" kind of interactions. This would also crash if it was an inner class of an activity.
The proof is that you are passing the context. Another question because that might the problem too. This is the context of the activity or the context of the AsyncTask? What context does it require?
I see that this is a very old thread but I would like to put in some efforts to answer this for those who are referring to this in future.
I had a similar problem and the below steps fixed the issue.
You can avoid or workaround this by appropriately setting,
<meta-data android:name="DOMAIN_PACKAGE_NAME" android:value="com.example" />
in your AndroidManifest.xml
It has been mentioned in the original guide that these parameters are all optional but without this parameter set, you may encounter the above exception.
I have also opened an issue on the main repo requesting to update the guide here
Are you using version 1.3?
I'm using version 1.3 not using DOMAIN_PACKAGE_NAME in my manifest and do the same thing inside an AsyncTask, actually i'm doing a lot of database work inside AsyncTask with a ProgressDialog...
If you want to stress out some things, my application tag in my manifest has the android:persistent="true" tag add.
Also my main activity has the android:launchMode="singleTask", but im also calling some AsyncTask from others Activity's.

openOrCreateDatabase in different class causes null pointer exception

I have defined a new class to use in my project and I get null pointer exception in the context
Here is what i coded :
public class OurClass extends Activity {
private dha mContext;
private dhaService sContext;
public OurClass(dha dha) {
sContext=null;
mContext = dha;
}
public OurClass(dhaService dhx) {
sContext = dhx;
mContext=null;
}
public void put_default_value( String varname, String value) {
Log.i("dha", "d1");
SQLiteDatabase db;
Log.i("dha", "d1.5");
if (mContext==null) {
Log.i("dha", "dx1");
db = sContext.openOrCreateDatabase("gipi.db", SQLiteDatabase.CREATE_IF_NECESSARY,null);
Log.i("dha", "dx2");
} else {
Log.i("dha", "dz1");
db = android.database.sqlite.SQLiteDatabase.openOrCreateDatabase("gipi.db", null);
Log.i("dha", "dz2");
}
You're not actually checking that the Contexts are valid before using them.
For example, if OurClass(dhaService dhx) is called with a null Context, then putDefaultValue will fail with a NullPointerException because sContext is assigned the value of dhx without checking either dhx or sContext for valid values.
On a side note, neither sContext, nor mContext are being initialised, which means that with the lack of proper checking you have right now, you could easily inadvertently call put_default_value, while never actually having called either of the constructors. This is not best practice.
EDIT:
One way to help guard against this is to make the default constructor private, so that an instance of the class must be created by calling one of the constructors you have provided:
private:
OurClass ( void ) {
}
Even if you do that, you still need to properly validate your inputs to at least detect null pointers - you can't just assume it will all work as you intend, since it clearly isn't right now.

NullPointerException in OnCreate of inherited Activity

Note: The title of this was changed to better reflect the actual problem.
I've run into a tricky nullPointerException - hoping someone here can give me an idea as to what is going wrong, as I am not successful in recreating the error so that I can get a debug stack trace.
The stack traces in the developer dashboard all indicate that the app is throwing a NullPointerException in onCreate from an Activity subclass (e.g., I have AActivity and BActivity both inheriting from BaseActivity which throws the exception). Presumably this is happening when the app is being resumed after having been thrown out of memory - at least that is my best guess. Although one user reports getting this error immediately on launching the app.
The onCreate function looks as follows:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.camp_ = MyApplication.getInstance().camp();
if (this.camp_ == null) {
this.finish();
return;
}
if (!this.camp_.isSane()) {
this.finish();
return;
}
}
That's essentially it. MyApplication is the Application for the app; getInstance returns a pointer to the instance, or throws an IllegalStateException if the instance is null. isSane() essentially checks whether some of the variables in this.camp_ are null and returns false if the latter is the case.
I can't for the life of me see how this can throw a NullPointerException, but... it apparently does. It's my most frequent cause of error reports at the moment - but I've so far not had any luck provoking the issue myself (a problem I've frequently had with these bugs that only occur when the app restarts after having been wiped from memory).
[Edit 1]
Example stack trace:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.michael.android.app/com.michael.android.app.gui.GreetActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2663)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
at android.app.ActivityThread.access$2300(ActivityThread.java:125)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4627)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.michael.android.app.gui.BaseActivity.onCreate(Unknown Source)
at com.michael.android.app.gui.GreetActivity.onCreate(Unknown Source)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
... 11 more
As mentioned BaseActivity is inherited, so there are several variants of this same basic pattern of stack trace. onResume essentially does the same check for validity of the this.camp_ object - there is no onDestroy or onPause code in BaseActivity.
[Edit 2]
The getInstance code looks as follows:
public static MyApplication getInstance() {
checkInstance();
return instance_;
}
private static void checkInstance() {
if (instance_ == null)
throw new IllegalStateException("MyApplication not created yet!");
}
If instance was null, it should be returning an IllegalStateException, not a NPE.
Not really sure this is relevant, but here's a snip of the Application class.
[Edit 3]
public class MyApplication extends Application {
// Instance
private static MyApplication instance_ = null;
private Camp camp_ = null;
public static MyApplication getInstance() {
checkInstance();
return instance_;
}
private static void checkInstance() {
if (instance_ == null)
throw new IllegalStateException("MyApplication not created yet!");
}
// Campaign
public Camp camp() {
return this.camp_;
}
private void parseSettings() {
if (getFileStreamPath("settings.xml").exists()) {
InputStream istream = null;
try {
istream = openFileInput("settings.xml");
/* Get a SAXParser from the SAXPArserFactory. */
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
/* Get the XMLReader of the SAXParser we created. */
XMLReader xr = sp.getXMLReader();
/* Create a new ContentHandler and apply it to the XML-Reader */
SettingsHandler handler = new SettingsHandler();
xr.setContentHandler(handler);
xr.parse(new InputSource(istream));
} catch (FileNotFoundException e) {
Log.e("MyApplication", "File not found exception: settings.xml");
} catch (Exception e) {
Log.e("MyApplication", "Exception thrown when decoding file settings.xml");
e.printStackTrace();
}
}
}
public void saveSettings() {
// ...
}
#Override
public void onCreate() {
super.onCreate();
// Set the instance
instance_ = this;
BaseActivity.flurryId = flurryId;
parseSettings();
}
public void setCamp(Camp c) {
this.camp_ = c;
}
}
I'm wondering if the issue may be something with the this reference being somehow incorrect in the onCreate methods.
In this code only MyApplication.getInstance() returning null would lead to a NullPointerException. If you want a more detailed explanation please supply the stacktrace and the MyApplication Singleton code.
I am not sure why you are implementing a Singleton pattern on the MyApplication class anyway. Your application is only going to run once, so no need for a Singleton. If you want to access your application instance in an activity you can use
MyApplication application = (MyApplication) getApplication();
Don't you have at least a stacktrace report? You're talking about a NPE but actually not where it is thrown. Sure you present the affected code?
My guess would be that you're relying on some code from previous activities, but since your application is restored the previous activities are not instantiated and the data is null. So I would recommend to search in onDestroy for such data dependent code because it will be called too.
Note that I had a BaseActivity (which is where the NPE occurs), which would be inherited by AActivity and BActivity.
Apparently, the issue was with calling "finish()" in the onCreate of BaseActivity. Moving the finish() calls out of the BaseActivity onCreate calls and instead checking whether to call finish() in AActivity and BActivity fixed the problem.
Hopefully this is of use to someone else. I will change the title to better reflect the actual problem.

Categories

Resources