I have a RecyclerView with its Adapter in a Fragment. Currently I'm hunting for OOM causes and Context leaking might be one of the cause.
There are several approaches I did in getting the context in Adapter (Need the Context for SharedPreferences, Glide/Picasso, and replacing Fragments).
Passing the Context through the adapter constructor and then set it into global variable inside the adapter :
LobbyAdapter lobbyAdapter = new LobbyAdapter(this.getActivity);
Have a global Context in the Adapter and take the Context from onCreateViewHolder :
context = parent.getContext();
This causes problem when I new the Adapter using SwipeRefreshLayout. But this must be because of the flawed logic I did in the Fragment, still tracking this down.
Don't make a global Context variable, but, get every Context from the View from every ViewHolder related to the Context
Loading an image
The key here is using the holder to get the Context ((FriendProfileViewHolder) holder).coverPhoto.getContext()
Glide.with(((FriendProfileViewHolder) holder).coverPhoto.getContext())
.load(utilities.webAddress + profileDataModel.user_cover_image_path)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.skipMemoryCache(true)
.centerCrop()
.into(((FriendProfileViewHolder) holder).coverPhoto);
In this part, due to my lack of experience with Context, I'm not sure which view should we get the Context if one method is reused by different Views
.
Additional question : (This might need new question thread..)
In several Adapters, I do an AsyncTask to get response from server to change image. And I need Context in the Interface to do getPackageName() to get package of the app, and getResources() to access resources.
String pictureName = output.image_name_profile;
String packageName = context.getPackageName();
if(!pictureName.equals("default")){
resId = context.getResources().getIdentifier("drawable/" + pictureName, null, packageName);
image = context.getResources().getDrawable(resId);
}
Maybe I should create a global variable and method to mutate those values?
I've used the first approach that you described in your question, the one for passing Context through a constructor and then set it to global variable for later use. This approach works great for Fragments containing RecyclerViews. Used it for a couple of times and it has never caused a Context leak problem.
I've not used any of the other approaches so will not comment on them.
Related
I am performing some reordering in a couple of array list, I have an adapter called
adapterMeasureEvi
which is set to a static ArrayList called measureEviArray from DataIpat class.
When debugging I can see that the static list is been assigned properly and it follows a notification to the adapter that the list has changed.
DataIpat.measureEviArray = (ArrayList<MeasureEvi>)measureEviArray.clone();
adapterMeasureEvi.notifyDataSetChanged();
Problem is, when getView() method gets called the first item it brings is from the old list, when I look up into the objects their indexes have changed that means I have updated the attributes but why is it still stuck on the old list?
/////EDIT////
I just noticed on the constructor of the adapter class that the list is definitely the old one.
public MeasureTableAdapter(Activity context, ArrayList<MeasureEvi> myMeasureEvi) {
super(context, R.layout.adapter_tablamedida_item, myMeasureEvi);
this.context = context;
this.myMeasureEvi = myMeasureEvi;
}
this constructor is called just once when the object is instantiated, so I suppose it means it will be stuck there, how can I update that list?
I think the problem is that when the data change, you recreate the DataIpat.measureEviArray instead of updating it. Hence your adapter will point to the old array, and the DataIpat.measureEviArray points to the newly upldated array. One way to fix your issue is instead of doing this (create a brand new array):
DataIpat.measureEviArray = (ArrayList<MeasureEvi>)measureEviArray.clone();
You should just update the DataIpad.measureEviArray array so that this array contains your new data (e.g. using clear and addAll to basically get the same effect as creating a new ArrayList).
Just stop cloning the list and work over the original worked, weird.
Deleted this,
DataIpat.measureEviArray = (ArrayList<MeasureEvi>)measureEviArray.clone();
not ideal but a workaround
Cheers,
I have an app that receives user input (2 numbers, width and height) and in theory depending on that input I have a custom view that should draw a grid (width and height).
Note:
These 2 values should be received before view attempts to draw itself.
These 2 values aren't constant and therefore I don't think XML approach can help.
I was told that adding another parameter to the View constructor is evil.
Do not confuse my 2 values with canvas.getWidth or etc.. these are values needed simply to draw something, nothing else.
My View is also a ViewGroup.
Main issue arises with Views declared in XML files.
I have temporarily solved this issue by making an SchemeContext class which contains those 2 static values and I simply set them in onCreate (before onCreateView) then use them in custom View onDraw when needed (SchemeContext.width). This is not really what people would call OOP I'm forcing global variables upon java and those are set on time because of the fragment lifecycle.
I've seen this answer How to pass variables to custom View before onDraw() is called?.
But it's more of a workaround than a solution (and probably not the fastest one). There has to be a sensible solution I don't think 3D games on android resort to these workarounds (SurfaceView with OpenGL is still a View right? :d).
If there is an obvious solution and this is an obvious double I'll remove the question.
I haven't tried this, but I think it would be possible to do this fairly cleanly by overriding the LayoutInflater.Factory. That way, you can intercept the creation of the views that need additional parameters passed to their constructors, and let the rest of them fall through to default inflation.
For example, in your activity, before you inflate the view hierarchy:
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
MyInflaterFactory factory = new MyInflaterFactory();
// Pass information needed for custom view inflation to factory.
factory.setCustomValue(42);
inflater.setFactory(factory);
For your implementation of the factory:
class MyInflaterFactory implements LayoutInflater.Factory {
public void setCustomValue(int val) {
mCustomVal = val;
}
#Override
public View onCreateView (String name, Context context, AttributeSet attrs) {
if (name.equals("com.package.ViewWithCustomCreation")) {
return new ViewWithCustomCreation(context, attrs, mCustomVal);
}
return null;
}
private int mCustomVal;
}
I was told that adding another parameter to the View constructor is evil.
Nonsense.
There are three (and in the newest APIs, four) different View constructors, each used in a different situation. (See this thread.) If you wanted to be able to declare your view in XML, for example, then you'd have to provide a constructor with exactly the right parameters, and have it call the corresponding superclass constructor. But there's nothing wrong with defining your own constructor (or even several of them) that call the superclass constructor intended for creating views programmatically.
The overriding principle is that every object must be valid when its constructor returns. So unless you can provide reasonable default values in your constructor, you have little choice but to accept the object's properties as constructor parameters.
Studying some (known to be good) code I can see the logic as follows:
if (getContext() instanceof Activity) {
inflater=((Activity)getContext()).getLayoutInflater();
}
else {
inflater=LayoutInflater.from(getContext());
}
I wonder, why this if/else, how it is better to, just, using LayoutInflater.from in all cases?
It doesn't really matter much.
Activity delegates getLayouInflater() to Window. The usual policy implementation of Window PhoneWindow in turn initializes its inflater with LayoutInflater.from(context) where context is the activity.
So the inflater object is really the same, using the same Context in case of an activity.
LayoutInflater.from() is really a wrapper for Context.getSystemService(). Now, system services are looked up by name from a map and then retrieved from a cache. This lookup has some overhead when compared to accessing an already initialized member variable in Activity.
So it smells like a micro optimization that does not likely affect much runtime performance when compared to actual view hierarchy inflation.
This optimization can actually have negative impact on developer productivity as people need to stop and spend some thinking why the code is there.
By the same code it seams that LayoutInflater.from is used in the contexts that are not an activity. I would assume that using the Activity's inflater reuses an already created inflater versus the other choice which would create a layoutinflater from a context.
The only change I would make is saving the context in a variable to prevent calling the same function and retrieving the same value from the object repeatedly:
Context ctx = getContext();
if(ctx instanceof Activity) {
inflater = ((Activity)ctx).getLayoutInflater();
}
else {
inflater = LayoutInflater.from(ctx);
}
Android has a lot of optimizations in place to reuse items when available, like the views for ListViews which can be reused.
from the android documentation it is suggested to use getLayoutInflater( ) instead.The documentation says the following about the LayoutInflator.from.. :
Instantiates a layout XML file into its corresponding View objects. It
is never used directly. Instead,
it suggests to use :
use Activity.getLayoutInflater() or
Context#getSystemService to retrieve a standard LayoutInflater
instance that is already hooked up to the current context and
correctly configured for the device you are running on.
in other words for the sake of simpler code and performance you better use getLayoutInflater from the context that has already been initilized.
Is it crucial for performance to have ViewHolder as static in a ViewHolder pattern?
A ViewHolder object stores each of the component views inside the tag
field of the Layout, so you can immediately access them without the
need to look them up repeatedly. First, you need to create a class to
hold your exact set of views. For example:
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
It's not crucial for performance, it is about using. If ViewHolder class will not be static - you have to provide instance of parent class:
No enclosing instance of type Type is accessible.
Must qualify the allocation with an enclosing instance of type Type
(e.g. x.new A() where x is an instance of Type).
Edit: misunderstood the question -- it seems you are asking specifically about making it static. That shouldn't be crucial for performance, but the idea is every bit helps.
Final edit here: Static nested class in Java, why?
====
Orig answer below:
It's very nice to squeeze performance out of a heavy ListView (or some other type of recycled AdapterView). However the best way to tell would be to profile the performance somehow.
Also at Google IO 2010 they recommend this method:
http://www.youtube.com/watch?v=wDBM6wVEO70
Edit:
Also here's a link to traceview to profile the performance, though I'm unsure how well it works.
http://developer.android.com/tools/debugging/debugging-tracing.html
It's not mandatory to do that. But when you use to do like this, you are using the views again when your adapter view is null. You are creating a view and assigning values to the view part, and tag the whole view using static class ViewHolder. So when you come back and view is not null, then visible part will come from to get the tag. This is how you will create less object as well less work load on adapter.
So far i've been writing my Android app just by typing names in to methods. I am now sorting this out, going through and putting these into string.xml instead and referencing the string using:
txt.setText(this.getString(R.string.string_name));
However, when trying to use this in a static context (in public static void), it does not work and gives an error.
Does anyone have any pointers of how to overcome this? I am fairly new to Java/Android programming and this is the first time I have come across this problem. Any help is much appreciated.
Extra code:
public static void ShowCatAddedAlert(Context con)
{
AlertDialog.Builder builder=new AlertDialog.Builder(con);
builder.setTitle("Add new Category");
builder.setIcon(android.R.drawable.ic_dialog_info);
DialogListner listner=new DialogListner();
builder.setMessage("Category Added successfully");
builder.setPositiveButton("ok", listner);
AlertDialog diag=builder.create();
diag.show();
}
Assuming that txt is a TextView, then you can just do txt.setText(R.string.string_name). You can usually reference to a string by it's resource id rather than getting it explicitly. More on that http://developer.android.com/guide/topics/resources/string-resource.html
String resources, as all resources, are resolved from the application from a Context instance (usually that's an Activity instance or the Application instance). In a static context, you don't have any instances unless you pass them in to your static methods.
One way or another, you need to do something in a non static context. Either you keep a copy of the Resources object around and pass it to your static methods, or you pass a Context instance around that is capable of resolving your resources, or you have a static Resources object that gets set at some point before your static methods get called.
That being said, you might want to revisit whether or not you absolutely need these methods to be static.