Safely deserializing LinkedHashMap in android - android

I have a LinkedHashMap that I want to pass through the Bundle savedInstanceBundle to store it between screen rotations. How do I do this safely? Before I just cast it because I know what I'm putting into it and what I'm getting out of it, but this did generate a warning that it was considered an unsafe cast.
What I am doing now :
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
if(thumbnails != null) {
savedInstanceState.putSerializable("thumbnails", thumbnails);
}
}
and retrieving it
thumbnails = (LinkedHashMap<Long, Bitmap>)savedInstanceState.getSerializable("thumbnails");
What is the correct way to deserialize LinkedHashMaps from a Bundle? Can it be done at all? If not, how do I preserve order without adding another layer in between that keeps track of the position which would involve changing a lot of existing code?
` Caused by: java.lang.ClassCastException: java.util.HashMap cannot be cast to java.util.LinkedHashMap`

From the MVC (Model -- View -- Controller) point of view, an Activity is a Controller. (And so is a Fragment.) The View is made from the XML and the Android View subclasses that you reuse (people rarely define custom View subclasses). And the Model is --- well, you have to define a class for the Model yourself! If you had Model, you'd look from a different perspective.
But if you nevertheless want to pass data from one Activity incarnation to another -- well, why don't you use a JSONObject/JSONArray? It's an overkill, it will be slow, but it should at least work.
Another possibility is to convert the LinkedHashMap into a list of key-value pairs and later reconstruct it from that list.

Related

How to prevent memory leaks when using dynamic screens?

I'm creating an app that has screens that present data to the user.
Each Screen has its own data and its own layout, so it has a method to return an int that represent the layout that is used to inflate it, then this View is passed to a function to find the specific views and populate it with data.
The lifecycle goes like this:
MainPresenter:
screen.getNextScreen ->
screen.getLayout ->
view = inflateScreen ->
screen.populateScreen(view) ->
(wait for time elappsed or click) -> repeat
Those Screens are also needed in SettingsActivity to enable\disable them.
So i've created a singleton ScreenProvider, it initializes once and then returns the list.
public class ScreenProvider {
private List<Screen> screens;
private static ScreenProvider instance = new ScreenProvider();
public static ScreenProvider getInstance(){
return instance;
}
private ScreenProvider() {
screens = new ArrayList<>();
screens.add(new Welcome());
screens.add(new CompoundScreen());
screens.add(new Times());
screens.add(new Messages());
screens.add(new Weekly());
}
public List<Screen> getScreenList() {
return Lists.newArrayList(screens);
}
}
Its seam that when running to long the app crashes or get closed for memory leaks, so i've added leakcanary, and this an example for its report:
MainActivity has leaked:
D: * static ScreenProvider.!(instance)!
D: * ↳ ScreenProvider.!(screens)!
D: * ↳ ArrayList.!(array)!
D: * ↳ array Object[].!([0])!
D: * ↳ CompoundScreen.!(disposable)!
D: * ↳ LambdaObserver.!(onNext)!
D: * ↳ -$$Lambda$Screen$67KdQ1jl3VSjSvoRred5JqLGY5Q.!(f$1)!
D: * ↳ AppCompatTextView.mContext
D: * ↳ MainActivity
This is just a single example, but almost every screen has such leak.
The LeakCanary report shows that TextView has this: D: | mAttachInfo = null so i assume it is not the problem.
Also every Screen has an onHide() to clear disposables, that is called when current Screen hides and in MainActivity.onStop().
How to fix this leak?
Should i not use a singleton for the screens?
If not, how do i access the screen list from other activities?
** Editing **
Adding some of Screen main methods that every screen overrides.
public abstract int getLayout();
public boolean shouldShow()
public void populateData(View view)
public void onHide()
public abstract int getScreenIndex();
public boolean shouldCacheView()
public int getDuration()
Okay. From what you are saying, and what you are showing, it seems you are keeping instances of some of the generated views into the Singleton. Don't. Every View requires a Context to be created, either by code, or by inflation (which is basically an XML-backed, reflection-based factory method), to access the app and system's resources, and keep a reference to said Context, for as long as they live. In your scenario, that means keeping references to the activity where you generated the view. Usually, regarding views and activities, this is what happens regarding the GC:
GC: Hey! Does anybody need this... MainActivity class?
View: I do! I do! I have a reference!
GC: Okay... and besides MainActivity, Does anybody else need this View class?
-Nobody answers-
GC: It does not matter my friend, you are being collected as well. Come with me.
And they both go.
In your case:
GC: Hey! Does anybody need this... MainActivity class?
View: I do! I do! I have a reference! and MainActivity references me as well.
GC: Okay... and besides MainActivity, Does anybody else need this View class?
ScreenProvider: I do.
GC: Okay, keep moving View, and take MainActivity with you. Let me know when you folks are done so I can collect you.
And thus the leak.
In order to pass a view from one activity to another, you would need to remove the reference (the mContext field) to the previous activity. Since there's no API for doing that, you would need to use reflection. And there arises another problem: Every UI piece is a subclass of View. Layouts, Widgets, etc, etc. So either you keep a reference to every piece of your XML file in order to remove the context via reflection, or you traverse the child list of the view, remove the context, and keep going until there are no more child views, at any level. After that, you would have to set the reference to the new activity in the same way. It sounds like a huge hack, because it is, and things are bound to break at some level. A Context after all represents the environment and state within your view exist.
A better solution for your situation is to remove the view references from the singleton, and use it only to keep representations of the state/configuration of a given view. Create a callback backed method (or similar) that inflates the view in background and performs the necessary configurations before returning said view. If you still want to keep a single repository of all the Screens the activity may have, add it to the activity class as a member, so it gets collected alongside the activity.
As a side note, your situation suggests you should use a single activity, and then just swap a "MainScreen" composed of "Screens", or just switch between screens depending on the situation. It would make more sense and would be less risky.
Finally, citing myself: Remember the first rule of the android fight club

What is the correct moment to call Dispose() when using the ViewHolder pattern?

I use the ViewHolder pattern as described by James Montemagno
https://blog.xamarin.com/creating-highly-performant-smooth-scrolling-android-listviews/
Considering the objects called by FindViewById, when should I call dispose? What is safe and correct to do. I should do it at some moment:
https://developer.xamarin.com/guides/android/advanced_topics/garbage_collection/
To shorten object lifetime, Java.Lang.Object.Dispose() should be invoked. TThis will manually "sever" the connection on the object between the
two VMs by freeing the global reference, thus allowing the objects to
be collected faster.
ListView is pretty old(Android 1.0). It was tightly coupled and not built with performance in mind. Lots of hacks were needed to keep it relevant. RecyclerView fills that gap.
https://www.youtube.com/watch?v=LqBlYJTfLP4
As for when you should call Dispose(), RecyclerView should handle this basic functionality via the LayoutManager. On the ViewHolder side, you can follow a basic Dispose pattern:
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
if(ItemView != null)
{
ItemView.Click -= HandleClick;
}
_listener = null; //Listener might just be a simple Action<int> like in this example: https://github.com/xamarin/monodroid-samples/blob/master/android5.0/RecyclerViewer/RecyclerViewer/MainActivity.cs#L111
}
In which you only care about Disposing the base and any Event Handlers you set up. However if your RecyclerView uses a bit of images in the View it's inflating, you will want to ensure you properly manage those resources as the Android GC will not be able to collect these items as they will be referenced by Xamarin.Android's GC(And they will be a small reference like a few bytes worth). Somehow you have to sever the link between the two GCs so it can be eligible for collection. You can read more about the GC Algorithm here: Xamarin Android garbage collection algorithm and the cause here: https://developer.xamarin.com/guides/cross-platform/deployment,_testing,_and_metrics/memory_perf_best_practices/#Use_Weak_References_to_Prevent_Immortal_Objects
To do that, we can sever the relationship via a couple ways:
MyObject = null;
MyObject.Dispose();
Either way should mark these items eligible for GC. In the case of Drawable you may also want to set the respective ImageView or object that the Drawable is being set in to null such as SetBackgroundDrawable/etc.
TLDR; Use RecyclerView, and remember to manage any Bitmap/Drawable resources appropriately.

Restoring an ArrayList of custom objects

In my app (which is a game), I have an 'Enemy' class, for example like so:
public class Enemy extends Sprite implements Serializable {
public Enemy(EnemyType type){
super();
}
}
I have then declared an ArrayList like so:
ArrayList<Enemy> enemyList = new ArrayList<Enemy>();
To which I can add enemies:
enemyList.add(bird);
enemyList.add(bee);
When saving to the Bundle I simply put:
bundle.putSerializable("Enemies", enemyList);
And when restoring from the Bundle, I have this:
enemyList = (ArrayList<Enemy>) savedState.getSerializable("Enemies");
Now, it does seem to restore the arraylist (I can check it's size and it is always correct - ie, the same size on restoring from the bundle, as it was when saving to the bundle.
I have also logged for example, the first index of the ArrayList and sure enough it lists the enemy instance as being there.
However, if I try to manipulate the ArrayList at any time post-restoration, I get an exception telling me that I'm trying to perform [whatever action] on a Null object (enemyList).
If I simply populate the list myself, so have something like:
enemyList = (ArrayList<Enemy>) savedState.getSerializable("Enemies");
enemyList.add(bird);
enemyList.add(bee);
Then everything works as expected.
I'm assuming this has something to do with the fact that the super class of Enemy isn't serialised? However, if I serialise this, I get a 'notSerializableException' error.
Please note, I'm not really too worried about saving/restoring the actual Enemy objects to the Bundle, I can handle this manually. Rather I just want the list to be in the same state as it was. And I thought that what was stored in the ArrayList were just references to the objects in question, therefore I can't work out why this is happening?
Any ideas what I'm doing wrong or is there a better method to achieve that which I'm trying to achieve?
The recommended way of doing this in Android is to make the objects you want to persist to be Parcelable. This is a type of serialization specific to Android.
Have a look at the official documentation here

Check if view equals to another

I have a view which I get from the parent ViewGroup:
mActiveCard = getChildAt(LAST_OBJECT_IN_STACK);
I later what to check if mActiveCard equals to another view:
anotherCard = getChildAt(x);
A naive approach would have been to check if x== LAST_OBJECT_IN_STACK however there might be many changes in the ViewGroup e.g. removed objects. So the positions are relative.
Also I can save the object for later but that will consume some more memory, like:
mActiveCard.equals( getChildAt(LAST_OBJECT_IN_STACK) )
One idea is to setId() of the view or setTag(). So if I have a unique String/int then I could later get the id or the tag. So saving just the id/tag would require less memory, right?
First of all is my theory correct? Also, does Android SDK offer a way to identify tags and what can be an good id/tag to generate and set on that view?
If you are truly holding onto a reference to a View (mActiveCard) that you know is also in the child list, you can simply compare their references.
if (mActiveCard == getChildAt(x))
{
// ...
}

DispatchKeyEvent: determine the exact type of the getCurrentFocus view

I have a custom activitygroup that adds and removes views to the stack.
I override the dispatchKeyEvent to check for certain keys pressed.
the problem is that I need to check the type of the current focused view like this:
View v=getCurrentFocus();
when I check for the type of the view,
it returns a type like this
com.android.internal.policy.impl.PhoneWindow$DecorView
what is this type and can I cast any class object to this type ?
thanks
No you can't just cast any class object to this type! Best practice is that you should only ever cast up the class hierarchy, or if you really must cast down make sure you check the type first using the instanceof operator.
Also, judging by the package name it's an internal android class, so you do not want any references to that in your code - if it's internal, it's non api. They could change it at any point and your app would immediately be broken.
Why are you checking the type of the view?
Are you looking for a particular type?
If so, it's be safer to do (for example):
View v = getCurrentFocus();
if(v instanceof SomeView){
//do stuff
}
Or if it is the decor view you are interested in use getDecorView instead of getCurrentFocus .

Categories

Resources