Search text and image OutOfMemory Exception - java

i implemented a search text for my list,
Every list row item includes a text and image that i downloaded from server.
i alsways keeps two list, one is the original List of text and images and other one is the sorted Lists of text and image...
if my list containes only 4 items, so my app works well, however if my list containes 20 items and every image is 1MB and i need to keep two lists listImagesOriginal , image_array inside adapter ( = 40MB) i recieve a OutOfMemory Exception.
how can i reduce the memory consuption that i will not recive a OutOfMemory Exception?
thanks alot
how can i reduce the
private MyCustomAdapter adapter;
private List<String> text = new ArrayList<String>();
private List<Bitmap> listImages;
private List<String> textOriginal = new ArrayList<String>();
private List<Bitmap> listImagesOriginal =new ArrayList<Bitmap>();
adapter =new MyCustomAdapter(text,listImages);
ListView.setAdapter(adapter);
searchText= (EditText) findViewById(R.id.profile_page_search);
searchText.addTextChangedListener(new TextWatcher()
{
public void afterTextChanged(Editable s)
{
// Abstract Method of TextWatcher Interface.
textlength = searchText.getText().length();
adapter.deleteAllForSearch();
for (int i = 0; i < textOriginal.size(); i++)
{
if(textOriginal.get(i).contains(
searchText.getText().toString().trim()))
{
adapter.addObjectForSearch(textOriginal.get(i), listImagesOriginal.get(i));
}
}
}
public void beforeTextChanged(CharSequence s,
int start, int count, int after)
{
// Abstract Method of TextWatcher Interface.
}
public void onTextChanged(CharSequence s,
int start, int before, int count)
{
}
});
class MyCustomAdapter extends BaseAdapter{
public List<String> text_array = new ArrayList<String>();
public List<Bitmap> image_array = new ArrayList<Bitmap>();
public int getCount(){
return text_array.size();
}
MyCustomAdapter(List<String> text, List<Bitmap>)
{
text_array = text;
image_array = image;
}
public long getItemId(int position){
return position;
}
public String getItem(int position){
return null;
}
public View getView(final int position, View convertView, ViewGroup parent) {
//my implementation
}
public void addObjectForSearch(String text, Bitmap bitmap) {
text_array.add(text);
image_array.add(bitmap);
notifyDataSetChanged();
}

40MB is way too much for android. Every phone has a per app memory limit, 40MB is higher than even most high end phones use. You need to implement disk and/or memory caching of your images.

You need to use a cache and/or store them into persistent memory (DB, filesystem, etc), as you don't want all those bitmaps loaded into memory at the same time, especially if you don't see them all at the same time. Many older devices have VM heap sizes around 16-24MB, and you need to be able to stay within a reasonable amount of memory usage.
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

Related

Prevent image reload on filtering adapter data [Android]

I am calling the filter data function from the fragment search view , it is working fine and the data are getting filtered but the images are getting reloaded.How can this be prevented
public menuadapter(ArrayList<GridItem> mGridDat, Context context, OnItemClickListener listener) {
this.mGridData=new ArrayList<GridItem>();
this.orignallist=new ArrayList<GridItem>();
mGridData.addAll(mGridDat);
orignallist.addAll(mGridDat);
this.context = context;
this.listener = listener;
this.Session=new session(context);
}
public void onBindViewHolder(final MyViewHolder holder, final int position) {
final Activity activity = (Activity)context;
String capital=mGridData.get(position).getTitle().substring(0,1).toUpperCase()+mGridData.get(position).getTitle().substring(1).toLowerCase();
holder.txtview.setText(capital);
Picasso.with(context).load(mGridData.get(position).getImage()).fit().centerCrop().skipMemoryCache().into(holder.imageView);
}
Filter Data function
public void filterData(String query){
query=query.toLowerCase();
//Log.v("check1",String.valueOf(orignallist.size()));
mGridData.clear();
if(query.isEmpty()){
mGridData.addAll(orignallist);
// Log.v("check2",String.valueOf(orignallist.size()));
}
else {
//Log.v("check0",String.valueOf(orignallist.size()));
ArrayList<GridItem> newlist = new ArrayList<>();
for(GridItem gd: orignallist) {
if ((gd.getTitle().toLowerCase().contains(query)) ) {
newlist.add(gd);
}
}
if(newlist.size()> 0){
mGridData.addAll(newlist);
}
}
notifyDataSetChanged();
}
Try this
Picasso.with(context).load(mGridData.get(position).getImage()).fit().centerCrop().networkPolicy(NetworkPolicy.OFFLINE).into(holder.imageView);
You need to use below property and not skipMemoryCache()
OFFLINE
public static final NetworkPolicy OFFLINE
Forces the request through the disk cache only, skipping network.
https://square.github.io/picasso/2.x/picasso/com/squareup/picasso/NetworkPolicy.html
To avoid reloding of images, solutions I think of are
one is to remove the images which are not matching the query and keeping others. Something like -
if(!(gd.getTitle().toLowerCase().contains(query)))
{
// get it from holder.getAdapterPosition();
contentsArrayList.remove(position);
notifyItemRemoved(position);
}
// out of for loop
notifyItemRangeChanged(firstRemovedPostion,contentsArrayList.size());
You can hide the element which is not matching the query and then show if it's matching another
itemView.setVisibility(View.GONE);

MediaPlayer doesn't work when it is not declared private

Reposting question: As i didn't get an answer for 15 days and i don't have enough bounty to shelve off.
I am a beginner to android programming and am trying to create an app which plays a certain sound.when the ListView item is clicked on, however when I declare the MediaPlayer object (as given below) inside the onItemClick method the app crashes.
phrasesActivityListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Word currentWord = phrases.get(position);
MediaPlayer mediaPlayer = MediaPlayer.create(PhrasesActivity.this, currentWord.getAudioResourceId());
mediaPlayer.start();
}
});
where the phrasesActivityListView is the name of the ListView
whereas when i declare a the MediaPlayer object as private the app works(as shown below )
private MediaPlayer mMediaPlayer;
.
.
phrasesActivityListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Word currentWord = phrases.get(position);
mMediaPlayer = MediaPlayer.create(PhrasesActivity.this, currentWord.getAudioResourceId());
mMediaPlayer.start();
}
});
why? And I have also included the Word Class below
package com.example.android.miwok;
public class Word {
//Default translation of the word
private String mDefaultTranslation;
//Miwok translation of the word
private String mMiwokTranslation;
//Resource id of the image to be shown on the screen.It is set to minus one so that it can be verified that whether the resource id is associated with any ImageView.
private int mImageResourceId = -1;
//Resource id of the audio file that is going to be played when the user click on the ListView item.
private int mAudioResourceId;
//Constructor to intialise the values of text in the textViewx
public Word(String defaultTranslation, String miwokTranslation, int audioResourceId) {
mDefaultTranslation = defaultTranslation;
mMiwokTranslation = miwokTranslation;
mAudioResourceId = audioResourceId;
}
//Constructor to intialise the values of text and the resource for the TextView and the ImageView.
public Word(String defaultTranslation, String miwokTranslation, int imageResourceId, int audioResourceId) {
mDefaultTranslation = defaultTranslation;
mMiwokTranslation = miwokTranslation;
mImageResourceId = imageResourceId;
mImageResourceId = audioResourceId;
}
//Returns the defaultTranslation of the word
public String getDefaultTranslation() {
return mDefaultTranslation;
}
//Returns the Miwok translation of the word.
public String getMiwokTranslation() {
return mMiwokTranslation;
}
//Returns the image resource id of the image.
public int getImageResourceId() {
return mImageResourceId;
}
//Checks whether the resource view has been intialised when creating a ImageView.
public boolean hasImage() {
return mImageResourceId != -1;
}
//Returns the audio resource id of the audio.
public int getAudioResourceId() {
return mAudioResourceId;
}
}
In your first code snippet, mediaPlayer is a local variable. It goes out of scope as soon as onItemClick() returns, and it will be eligible for garbage collection. Since you want that object to live for a bit longer than that, you need to hold onto it somewhere else, such as inside the activity or fragment in which this code resides.
The way you are preserving the object of Your list view phrasesActivityListView, in the same way You need to declare Your media player object. So that it can live longer to play whatever You wants to play.
More You should read the lifecycle of media player also. It will make clear how You should deal with media player object like release the object, sequence of prepare and start.

Wait for completion of a calculation without UI freeze

I am trying to implement a search function in an Android app that takes text from an AutoCompleteTextView, waits if there hasn't been made a change in the last 1.5 seconds and shows the search results. For this I use the TextWatcher class.
However, all my tries to implement this behavior ran into trouble with some functions only being allowed in the UI thread itself (via runOnUIThread) or the thread having Looper.prepare() called before.
In all attempts, the app crashes randomly when entering additional characters or deleting some, does not show any search results or reload to the start activity.
The following is a simplyfied recreation of my most recent try, where I use a Handler.
search.getResults is the long computation and matches is an array that has to be filled before delayableAdapterCreation creates the ArrayAdapterWithSpaceFilter.
public class SearchFragment extends Fragment {
public final static int MAX_NUMBER_OF_SUGGESTIONS = 4; // only show a max of 4 suggestions if more were found
public final static int SEARCH_CHAR_AMOUNT = 3; // only search if at least 3 characters were typed
public final static long SEARCH_DELAY_MILLIS = (long) 1500; // the time to wait for no text changes in milliseconds
private Search search;
private AutoCompleteTextView textView;
private String[] matches;
private String userStartRequest;
private Entry[] suggestions;
private FragmentListenter sListener;
private EntryFunctions ef = new EntryFunctions();
private Runnable delayableSearch;
private Runnable delayableAdapterCreation;
private Handler delayableSearchHandler;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
delayableSearchHandler = new Handler();
delayableSearch = new Runnable() {
#Override
public void run() {
userStartRequest = textView.getText().toString();
sListener.onFragmentFinish(userStartRequest);
suggestions = search.getResults(userStartRequest);
matches = ef.fillMatches(suggestions);
}
};
delayableAdapterCreation = new Runnable() {
#Override
public void run() {
ArrayAdapterWithSpaceFilter<String> adapter =
new ArrayAdapterWithSpaceFilter<String>(getActivity(),
android.R.layout.simple_list_item_1,
matches);
textView.setAdapter(adapter);
}
};
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_search, container, false);
}
#Override
public void onStart() {
super.onStart();
textViewHandler();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(context instanceof FragmentListenter)) throw new AssertionError();
sListener = (FragmentListenter) context;
}
/**
* Interface for communicate to activity
*/
public interface FragmentListenter {
void onFragmentFinish(String userStartRequest);
}
/**
* Handler for the AutoCompleteTextView
*/
private void textViewHandler() {
try {
textView = (AutoCompleteTextView) getView().findViewById
(R.id.startNaviAutoCompleteTextView);
search = new Search();
System.out.println("Created Search object");
textView.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
System.out.println("TextWatcher beforeTextChanged");
}
#Override
public void onTextChanged(CharSequence s, final int start, int before, int count) {
delayableSearchHandler.removeCallbacks(delayableSearch); userStartRequest = textView.getText().toString();
sListener.onFragmentFinish(userStartRequest);
if (textView.getText().length() >=
SEARCH_CHAR_AMOUNT) {
new Thread(delayableSearch).start();
delayableSearchHandler.postDelayed
(delayableAdapterCreation, SEARCH_DELAY_MILLIS);
}
}
#Override
public void afterTextChanged(Editable s) {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
At this point, it does not matter to me, whether the calculation already starts whenever a new character is typed into the AutoCompleteTextView and an eventual old search is canceled or the search starts after the 1.5 seconds.
The above code does crash if the search term yields no results and there are problems with the results list. Sometimes it shows up for what has been entered a few keystrokes ago (so if I search for abcd slowly I get search results for abc), sometimes it doesn't show up at all. My guess would be a race condition or some problem with calling the textViewHandler or onTextChanged methods multiple times, even though delayableSearchHandler.removeCallbacks(delayableSearch) should prevent this from happening.
Can anyone explain, what the interaction between the worker thread and the UI thread would have to look like, so it is guaranteed that the search delivers it's results?
Thanks in advance,
Joe
Any long running operation (Network call, database search...) can take long time to execute thus blocking the UI. Prior to Ice cream sandwich this kind of behavior was tolerated by the android runtime.
This article might be a good read

Load more data into ListView - Android

I have implemented a listview in which data is loaded through an asynctask, To load more data I have used this code found here (exactly copy pasted)
Main Activity
class load_data extends AsyncTask<Integer, Void, Void>{
// This asynctask takes in a page number as argument and depending on that page number
// data is loaded and stored in various String[] arrays and their length is extended as new items are loaded
protected void onPostExecute(Void result) {
if(adapter == null){
adapter = new Listadapter(Main.this,String[],String[],String[]);
list.setAdapter(adapter);
}
else if(adapter != null){
adapter.notifyDataSetChanged();
}
}
}
This task is called as new load_data().execute(1);
after this code I have the load more data snippet from the above link
everything works perfectly no syntax errors, and also the data loads after reacing the given threshold (20) in my case, however new data is not shown. how do I notifiy the adapter that more data has been added or data has been changed
Thanks!.
EDIT: LOADMORE CLASS
class EndlessScrollListener implements OnScrollListener {
private int visibleThreshold = 5;
private int currentPage = 1;
private int previousTotal = 0;
private boolean loading = true;
public EndlessScrollListener() {
}
public EndlessScrollListener(int visibleThreshold) {
this.visibleThreshold = visibleThreshold;
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
currentPage++;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// I load the next page of gigs using a background task,
// but you can call any function here.
new load_data().execute(currentPage);
adapter.notifyDataSetChanged();
loading = true;
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
list.setOnScrollListener(new EndlessScrollListener(20)); // 20 being the threshold
Finally! for those who are stuck with the same problem, I have the solution.
I have noticed that adapter.notifyDataSetChanged(); does not work if you are using String[] arrays to store data which is displayed into the listview rows.
Instead if you use List<String> list = new ArrayList<String>();and store data in the
the listview will be updated with new data :)
you can check this github project if you have any problem with your previous code.
otherwise if you want to load data like pagination you can try this example and this one

ListView custom adapter for different rows

I am using a ListView to display some JSON data and want to display each result according to its type (Artist, Release, Label...).
I will be using an interface implemented by each type of result :
public interface Result {
public Int getId();
public String getThumb();
// ...
}
I would like to know which of these choices is the best solution (I am open to better things, that's just what I had on the top of my head):
creating an enum ResultType in the interace (so inherited class will have to return their own value like ResultType.ARTIST in a getType() method
checking the instance type using isInstance()
I would like to know what would be the best way to perform something equivalent to this C code (array of function pointer) as I would like to avoid using to many if/else statements.
typedef struct s_func {
const char *type_name;
void* (*func_pointer)(void *result_infos);
} t_func;
static t_func type_array[] = {
{"artist", artist_function},
{"label", label_function},
// ....
{NULL, NULL}
}
void check_type(const char *type_string)
{
int i, j = 0;
char *key_value;
// compare string and array key
while (type_array && type_array[i][0]) {
key_value = type_array[i][0];
// if key match
if (type_string && strncmp(type_string, key_value, strlen(type_string)) == 0) {
type_array[i][1](); // call appropriate function;
}
i++;
}
}
I guess it would be using a HashMap but (I might be wrong) it doesn't seem to have a litteral notation. Is there any easy way to build an HashMap of pairs ?
Thank you
I think you can use an ArrayAdapter.
Take a look at this tutorial to see what I mean.
It'll need some twiddling so that it can deal with the different kinds of items.
Make an interface MyListItem
public interface MyListItem {
public int getLayout();
public void bindToView(View v);
}
Make different layouts for the display of Artist, Release, Label.
Make classes Artist, Release, Label that implement MyListItem.
public class Artist implements MyListItem {
private String Name;
public Artist(String name){
this.name = name;
}
public int getLayout() {
return R.layout.artistlayout;
}
public void bindToView(View v) {
TextView textView = (TextView) rowView.findViewById(R.id.artistLabel);
textView.setText(name);
}
}
Now the adapter only has to call the right methods to fill the view for the selected item.
public class MySimpleArrayAdapter extends ArrayAdapter<MyListItem> {
private final Context context;
private final MyListItem[] values;
public MySimpleArrayAdapter(Context context, MyListItem[] values) {
super(context, android.R.layout.simple_list_item_1, values);
this.context = context;
this.values = values;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
MyListItem item = values[position];
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(item.getLayout(), parent, false);
item.bindTo(view);
return view;
}
}

Categories