In my app I have a profile pic of the user. If he previously logged in, I download it from my server, otherwise he takes a pic from the camera or gallery. Either way, I store it in a class called DataStorage in a static public Bitmap variable thus:
public class DataStorage {
.
.
public static Bitmap origProfilePic; //not saved on app close
.
.
Now, if the user decides to re-do his profile, I log him out send him back to the profile creation screen, and I call:
DataStorage.origProfilePic = null;
(By the way, null is a legitimate value for this picture - we allow users to have no picture). The profile creation is through 2 activities - first choosing a username and password, then choosing a picture and other details. What's weird is that although I set the picture variable to null, somehow when I get to the end of the profile creation, it has gone back to it's original value, and I somehow have a user with the old profile picture (this is in the case where he did not select a new picture)
I've logged it n the OnCreate functions of the two activities:
onCreate SignUpActivity1, origPic = null
then
onCreate SignUpActivity2, origPic = android.graphics.Bitmap#42ba7438
But SignupActivity1 calls the intent for SignUpActivity2 and nowhere in SiugnupActivity1 is there a reference to DataStorage.origProfilePic except in my log statements.
I decided to check one more time in SignUpActivity1 if the value changes and I found...just as I invoke SignupActivity2:
Intent intent = new Intent(SignUpActivity1.this, SignUpActivity2.class);
intent.putExtra("EMAIL", email.toString());
intent.putExtra("PASSWORD", password.toString());
startActivity(intent);
Log.e("Kibi", "End SignUpActivity1, origPic = " + DataStorage.origProfilePic);
finish();
This somehow has:
End SignUpActivity1, origPic = android.graphics.Bitmap#42ba7438
This is quite repeatable in this specific case, though I certainly set the bitmap to null in other places and don't see it popping back up.
I saw a few things on SO about static Bitmaps being a danger for memory leaks, though I don't see a memory leak right now, I worry that I must be doing something very wrong if setting to null cannot be relied upon.
Can anyone explain what I am doing wrong and what the best way is to store this Bitmap in a basically volatile way?
It makes sense that both values change when you use DataStorage.origProfilePic in both activities. Since it's a static value, both activities are pointing to the same memory object.
DataStorage.origProfilePic is pointing to a piece of memory where your bitmap is stored. Assuming you use something like:
#SignUpActivity1
Bitmap origPic = DataStorage.origProfilePic;
#SignUpActivity2
Bitmap origPic = DataStorage.origProfilePic;
What happens here is that you create a reference to DataStorage.origProfilePic. That means you practically have the same value in SignUpActivity1.origPic and SignUpActivity2.origPic.
When you call this rule:
startActivity(intent);
The OnCreate method in SignUpActivity2 will be invoked. You probably will set here (or somewhere else) the value of DataStorage.origProfilePic, which means the value of SignUpActivity1.origPic and SignUpActivity2.origPic also changed (not entire true, but will have a different output).
Also be carefull with DataStorage.origProfilePic = null;. This only set the reference to null, but doesn't clear the bitmap from memory. Use DataStorage.origProfilePic.recycle(); to clear the bitmap from memory.
Related
I have an activity which can take a few seconds to load its content (mainly pictures) from the cloud. Let's say this content is pictures & description from a person. The user can go to pictures & description from another person by clicking on the next button. I'd like to avoid the loading time When this button is clicked.
To do this I have two activities : firstPersonActivity for the first person content, and secondPersonActivity for the second person content.
What I try to do is to load the content of the secondPersonActivity when the user is in the firstPersonActivity, so that the secondPersonActivity can be displayed "directly" (= without needing to load content from the cloud when the next button is clicked in the firstPersonActivity).
But I do not succeed in doing this, I don't know how to modify the views of the secondPersonActivity layout from the firstPersonActivity class.
I tested the following code in my firstPersonActivity but it doesn't work (there is no connexion to the cloud, it's just a test to better understand how it works). R.id.first_image_second_person is the id of my imageview in the secondPersonLayout (= the layout used in the secondPersonActivity).
ImageView firstImageSecondPerson = (ImageView) findViewById(R.id.first_image_second_person);
firstImageSecondPerson.setImageResource(R.drawable.mypicture);
When I click on the next button to go from the firstPersonActivity to the secondPersonActivity, my firstImageSecondPerson imageview is not filled.
Do you see what's wrong ?
Is there a better way to avoid the loading time when the user click on the next button ?
It is not possible to change activity if activity instance is not created. In my opinion to do what You need I would go to single activity with hidden content of second person ( VIEW.INVISIBLE ) and show/hide it when it is needed.
But if second activity must be there, then create some structure for saving bitmaps in memory. In Your code sample You get picture from drawable so we are not talking about some delays, but if images are downloaded from some server then You can create some class which will have those bitmaps created from url and instance of this class can be used on any activity.
So for example Your class for caching pictures would have some method for creating bitmaps like -How to load an ImageView by URL in Android?. And those bitmaps should be saved in some Array or HashMap to have access to it, for example ( pseudo code ):
//some structure for cashing pictures in app
class PictureCache {
static PictureCache instance;
final HashMap<Integer, Bitmap> bitmaps;
//singleton
static PictureCache getInstance(){
if (instance==null)
instance = new PictureCache();
return instance;
}
public PictureCache(){
bitmaps = new HashMap<>;
}
private Bitmap loadBitmap(String url);//#1
public addPicture(Integer personId, String href){
//add to hashMap
bitmaps.put(personId, loadBitmap(href));
}
public Bitmap getPicture(Integer personId){
return bitmaps.get(personId);
}
}
#1 - method from How to load an ImageView by URL in Android?
Using it in first activity:
PictureCache.getInstance().addPicture(12,"http://url.to.bitmap");
Using it in second activity:
Bitmap pic = PictureCache.getInstance().getPicture(12);
Important note - above code was written here and was not tested, it shows solution concept.
Important second note - using such approach with bitmaps in memory can cause to much memory usage
You cannot access the views of SecondActivity before its creation. If you called this activity once only then you are able to access its views by making them static.
One more Solution for this is..
Access the whole data at once and save it in static arraylist with the help of getter-setters. Then on SecondActivity set data from that arraylist.
Hope this will work.
I don't think you can access the view before creating the activity.
You can try to use Glide for caching your images and minimizing loading time.
Try this
Picasso.with(getContext()).load(R.drawable.generatedId).into(imageView);
or
imageView.setImageDrawable(ActivityCompat.getDrawable(getContext(),
R.drawable.generatedID));`
Context:
i have n dynamically created ImageView(s) in an activity.
when an imageview is touched:
1) an IV object reference is stored vis the onTouch event.
2) a dialog opens; allowing the user to edit the image.
when the user has finished making edits, the changes are applied to the object which we have a stored reference.
however, if the user rotates the device while the Dialog is open, the activity is recreated; and the reference to the selected ImageView is no longer valid.
Issue:
So, the problem is that the recreated ImageView, produced from the rotation event, has a different reference to the reference that i have stored, meaning that i have no idea which ImageView to apply my edits to..
This seems like a common issue that could easily be solved, but i don't know how..
because the ImageView(s) are dynamically created, and there could be n quantity, i can't find view by ID.
i can't set a tag, as it is lost when the IV is recreated.
i read something about retainInstanceState, but this appears to be for fragments; not activities
Assign ids to your generated ImageViews. When dialog is shown you pass this id to dialog, when dialog finishes it returns your values and this id, then you just get ImageView by id, be it a new one, or old doesn't matter since the couple 'id - iv at position' are the same no matter how many times you recreate activities.
As example:
for (int i=0;i<20;i++) {
ImageView iv = new ImageView(context);
iv.setId(1337 + i); // Spice things up a little :D
addView(iv);
}
Then just pass required id iv.getId() to dialog, and when dialog finishes get iv by calling findViewById(id);
The easiest way to fix this is to add android:configChanges="orientation|<whatever other flags you need>" to your <activity> in AnroidManifest.xml, so the activity won't be recreated when orientation changes. However, using this method is officially discouraged:
Note: Using this attribute should be avoided and used only as a last resort
Another way would be to store in the dialog not the reference to ImageView, but its id instead.
I have a ListActivity to let user pick photo(s) to be upload
For upload, I'll put photos inside a static ArrayList called PhotoList
After that, I'll start a Service to upload those photos
Now, user may switch to another activity to do some other things
Later, user will back to this ListActivity to check upload status. Also in here they can pick more photos to be upload.
So, my PhotoList is actually a kind of Queue, but it also a data to be display at ListActivity.
My problem is, when the Service is running, and user picked more photo(s), I would like put those photo(s) inside the PhotoList.
(I don't want to start Service again since the service is already running...)
Now I just get stuck here.
okey I will try to give you a solution based on my understanding to your problem :
You have a list with photos and you want the user to be eligible to upload those images and update it the user of it status ( uploaded/ uploading / upload/failed) and you dont want to start the Service every time you upload .
a Simple working solution is to use an IntnetService i will be running only if there is tasks assigned to it and will atomically shut down when finish the work and of course the job will be in a sperate thread when working with IntentService
step 1
make a database table contains data about the images
_id integer
_image_uri
_image_status :
_image_status will hold one of these values ( 1-uploaded : finish_uploaded , 2- uploading: service is uploading the image , 3-upload : the user can upload the image 4-failed: failed to upload you can retry )
step2
now in the UploadIntentService try to upload the image to server and when if the upload competes successfully or an error happened while upload update the database
public class UploadIntentService extends IntentService {
private static final String TAG = UploadIntentService.class.getSimpleName();
private static final int STATUS_UPLOAD = 0x01; //can be uploaded
public static final int STATUS_FAILED_TO_UPLOAD = 0x02; // tried to upload but failed
public static final int STATUS_UPLOADING = 0x03; // self explanied
public static final int STATUS_SUCCESSFULLY_UPLOADED = 0x04; // the image uploaded to server
public UploadIntentService() {
super(TAG);
}
#Override
protected void onHandleIntent(Intent intent) {
int status = intent.getIntExtra("status", -1);
String imageUri = intent.getStringExtra("image_path");
long imageDatabaseid = intent.getLongExtra("image_db_address",-1);
if(status != STATUS_SUCCESSFULLY_UPLOADED && status != STATUS_UPLOADING){
try{
//update _image_status column with value of STATUS_UPLOADING with the image_id = imageDatabaseid;
//upload code
//successfully uploaded
//update _image_status column with value of STATUS_SUCCESSFULLY_UPLOADED with the image_id = imageDatabaseid;
}catch(Exception ex){
ex.printStackTrace();
//update _image_status column with value of STATUS_FAILED_TO_UPLOAD with the image_id = imageDatabaseid;
}
}
}
}
......
step3
and you ListActivity if you want to upload any image use this code
Intent intent = new Intent(context, UploadIntentService.class);
Bundle uploadExtras = new Bundle(3);
uploadExtras.putLong("image_db_address", PUT HERE THE IMAGE DATABASE ID );
uploadExtras.putInt("status", PUT HERE THE IMAGE STATUS );
uploadExtras.putString("image_path", PUT HERE THE IMAGE PATH IN FILE SYSTEM);
intent.putExtras(uploadExtras);
context.startService(intent);
.......
step 4
make sure you declare the Service in the manifest.xml , and enjoy.
Your Service should override the onResume() function. onResume() is called whenever the user does other tasks, opens other activities etc and finally returns back to your application's activity. That is just the gist.
You can learn more about Runtime Changes . The concepts that apply for Activity apply for Service also, in that Service is just not "shown" to the user.
In the above link, you will learn when an activity is recreated, and when it is actually resumed by calling onResume(). You also need to override onSaveInstanceState() and onRestoreInstanceState() so as to pass state objects between your old Activity instance and the new one.
What you can do is, when the user goes leaves your activity, in your onSaveInstanceState(), you should save your state variables eg. your list of selected photos, in a Bundle. And pass this Bundle to onRestoreInstanceState()
Question: Are you actually starting your service as a valid Service ? I mean, are you extending the Service class? I've used Service for my projects, and they always resumed where they left without overriding anything. The reason: Services run in background, resources are not released until the service is done doing it's job. So if you have not used Service , then I recommend you do that. Your problems will be taken care of.
You can make a BlockingQueue in which you will store photos to upload. This BlockingQueue is your PhotoList. In Service, in loop, you will look into this BlockingQueue. Two cases are possible:
There is some photo. Take it and upload.
Queue is empty. So the thread will go to sleep, BlockingQueue assures this.
You can use this :
Copy On Write Array List
This will be a thread-safe random-access list.
Check this link for good explanation and example.
Difference between ArrayList and CopyOnWriteArrayList
Iterators of this list never throw ConcurrentModificationException. When an iterator is created, it keeps a copy of the list's contents. It is always safe to iterate this list.
In my app I have a header with icon hidden, I have a adapter with a listview when I click the listview I go to a login screen using listener, when the login is successful is should come back to listview(adapter) and icon should get visible on header.
In the login activity I have the following code:
public void onClick(View v) {
String password = etPassword.getText().toString();
if(password.equals("guest")){
SearchAdapter.setImgVisibility();
} else {
//-----
}
finish();
}
In my adapter I am calling the setImgVisibility() as follows, but it is not working
public static void setImgVisibility() {
img.setVisibility(View.VISIBLE);
}
I am getting a Nullpointerexception near the line img.setVisibility(View.VISIBLE);
I am stuck here and don't know what I am doing wrong. Any suggestions or help is appreciated
I would imagine that img is null. You need to look at where this value is set and make sure happens before you call the method setImgVisibility.
Show more of your complete code for people to help further.
Additionally, i've just noticed you've used a static reference to your search adapter, you should be really careful using statics, especially where any referencing of images is concerned as images can be bound to the context, as such unless you nullify the static you will end up with a memory leak. (this used to be an old problem, not sure its still valid, but i would still avoid using a static reference).
Without more code we're not likely to be able to properly help you. For example are you switching activities when logging in? If you are, this won't work at all.
[given the comment below] If you switch activities then your activity containing the list view is going to be destroyed and then rebuilt then you navigate back to it. or it will at least go through the activity lifecycle. This means you can set the icon during the instantiation of the header img.
You could store your logged in state as a property of the Application or a preference. Grab this value when you set the header image and set the image accordingly.
your img object is null. Is your img object is same as View v then you can pass v in setImgVisibility() and then set v.setVisibility(View.VISIBLE)
My app has this appearance
It seems to be a TableLayout with several TableRows. In my activity, each TableRow has 3 views: ImageView, TextView and a Button.
The user is the one who sets the content of the ImageView and the TestView by entering the text he wants and pressing a button.
I store this data in stringArray variables and works fine if the phone is not restarted or the app is not closed (forceClose)
If one of these two situations happen, i lose all my data.
I've been trying to store my StringArrays by SharedPreferences but I don't know when i should load the preferences, whether it's in OnCreate() or OnResume() or OnStart methods().
Another question is how to define the arrays. I use this:
String[] titulo = new String[500];
I don't know if this string is created each time i start the activity. Because what i want is to load the previous String (from SharedPreferences) and add some more entries not to create new ones every time the phone is rebooted, for example.
Do you think i need a SQL database or it's OK with this StringArrays.
Thank you.
You should probably switch to using a database, seeing as you have an array of size 500, which could possibly increase in the future.
However, if you want to continue using SharedPreferences, you should write the data in onPause() and onStop() methods, and use an if else statement to see if your data is null before running an operation on it. If the data is null, the read it from the SharedPreferences before continuing.