Universal Image Loader in most cases doesn't show image - java

I'm using universal image loader, to load images for custom info windows on my Google map. But in most cases it doesn't load images, it shows picture for empty uri.
Here's my code
In MainActivity:
initImageLoader();
markers = new Hashtable<String, String>();
imageLoader = ImageLoader.getInstance();
options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.loading)
.showImageForEmptyUri(R.drawable.empty)
.showImageOnFail(R.drawable.error)
.cacheInMemory(true)
.cacheOnDisc(true).bitmapConfig(Bitmap.Config.RGB_565)
.build();
map.setInfoWindowAdapter(new CustomInfoWindowAdapter());
ReadXML readXml = new ReadXML();
readXml.getXml(camera);
for (int i = 0; i<camera.size(); i++) {
Camera cam = camera.get(i);
LatLng coord = new LatLng(cam.lat, cam.lon);
String title = Integer.toString(cam.id);
String place = cam.place;
String url = cam.img;
Marker mark = map.addMarker(new MarkerOptions()
.position(coord)
.title(title)
.snippet(place)
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.green)));
markers.put(mark.getId(), url);
}
Custom Info Window Adapter:
private class CustomInfoWindowAdapter implements InfoWindowAdapter
{
private View view;
public CustomInfoWindowAdapter()
{
view = getLayoutInflater().inflate(R.layout.custom_info_window,
null);
}
#Override
public View getInfoContents(Marker marker)
{
if (MyMapActivity.this.marker != null && MyMapActivity.this.marker.isInfoWindowShown())
{
MyMapActivity.this.marker.hideInfoWindow();
MyMapActivity.this.marker.showInfoWindow();
}
return null;
}
#Override
public View getInfoWindow(final Marker marker)
{
MyMapActivity.this.marker = marker;
String url = markers.get(marker.getId());
final ImageView image = ((ImageView) view.findViewById(R.id.badge));
imageLoader.displayImage(url, image, options,
new SimpleImageLoadingListener() {
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
super.onLoadingComplete(imageUri, view, loadedImage);
getInfoContents(marker);
}
});
final String title = "Камера №" + marker.getTitle();
final TextView titleUi = ((TextView) view.findViewById(R.id.title));
if (title != null) {
titleUi.setText(title);
} else {
titleUi.setText("");
}
final String snippet = marker.getSnippet();
final TextView snippetUi = ((TextView) view
.findViewById(R.id.snippet));
if (snippet != null) {
snippetUi.setText(snippet);
} else {
snippetUi.setText("");
}
return view;
}
}
And Universal Image Loader:
private void initImageLoader()
{
int memoryCacheSize;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
int memClass = ((ActivityManager)
getSystemService(Context.ACTIVITY_SERVICE))
.getMemoryClass();
memoryCacheSize = (memClass / 8) * 1024 * 1024;
} else {
memoryCacheSize = 2 * 1024 * 1024;
}
final ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
this).threadPoolSize(5)
.threadPriority(Thread.NORM_PRIORITY - 2)
.memoryCacheSize(memoryCacheSize)
.memoryCache(new FIFOLimitedMemoryCache(memoryCacheSize-1000000))
.denyCacheImageMultipleSizesInMemory()
.discCacheFileNameGenerator(new Md5FileNameGenerator())
.tasksProcessingOrder(QueueProcessingType.LIFO).writeDebugLogs()
.build();
ImageLoader.getInstance().init(config);
}

Have you taken a look at what the following line actually sets the sring to?
String url = markers.get(marker.getId());
I would suspect if this works like other google maps implementations that marker is a json string/object with multiple pieces of information and that you need to get it and then get the url from it. Try and do some logcat logging to be sure, you've posted a lot of code so it's hard to tell which parts are relevant.

Related

getActivity(), getContext(), getApplicationContext(), getBaseContext(), activity.this, this :all returning NULL (not a duplicate of NullPointerExcept)

I have an activity called FiltersAndEditActivity and two fragments inside it. Inside one of the fragments I am running the following code:
public void prepareThumbnail(final Bitmap bitmap) {
Runnable r = new Runnable() {
public void run() {
Bitmap thumbImage;
if (bitmap == null) {
thumbImage = BitmapUtils.getBitmapFromAssets(getActivity(), FiltersAndEditActivity.IMAGE_NAME, 100, 100);
} else {
thumbImage = Bitmap.createScaledBitmap(bitmap, 100, 100, false);
}
if (thumbImage == null)
return;
ThumbnailsManager.clearThumbs();
// add normal bitmap first
ThumbnailItem thumbnailItem = new ThumbnailItem();
thumbnailItem.image = thumbImage;
thumbnailItem.filterName = "normal";
ThumbnailsManager.addThumb(thumbnailItem);
List<Filter> filters = FilterPack.getFilterPack(activity);
for (Filter filter : filters) {
ThumbnailItem tI = new ThumbnailItem();
tI.image = thumbImage;
tI.filter = filter;
tI.filterName = filter.getName();
ThumbnailsManager.addThumb(tI);
}
thumbnailItemList.addAll(ThumbnailsManager.processThumbs(context));
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
}
};
new Thread(r).start();
}
processThumbs method code (inside a Util class named ThumbnailManager):
public static List<ThumbnailItem> processThumbs(Context context) {
for (ThumbnailItem thumb : filterThumbs) {
// scaling down the image
float size = context.getResources().getDimension(R.dimen.thumbnail_size);
thumb.image = Bitmap.createScaledBitmap(thumb.image, (int) size, (int) size, false);
thumb.image = thumb.filter.processFilter(thumb.image);
// cropping circle
// TODO - think about circular thumbnails
// thumb.image = GeneralUtils.generateCircularBitmap(thumb.image);
processedThumbs.add(thumb);
}
return processedThumbs;
}
FiltersAndEditActivity code where I am calling prepareThumbmail method of fragment:
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapterr adapter = new ViewPagerAdapterr(getSupportFragmentManager());
// adding filter list fragment
filtersListFragment = new FiltersListFragment();
filtersListFragment.setListener(this);
// adding edit image fragment
editImageFragment = new EditImageFragment();
editImageFragment.setListener(this);
adapter.addFragment(filtersListFragment, getString(R.string.tab_filters));
adapter.addFragment(editImageFragment, getString(R.string.tab_edit));
viewPager.setAdapter(adapter);
}
static class ViewPagerAdapterr extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapterr(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
private void setImage(){
intent = getIntent();
FiltersListFragment filtersListFragment = new FiltersListFragment();
if(intent.hasExtra(getString(R.string.selected_image))){
imgUrl = intent.getStringExtra(getString(R.string.selected_image));
Log.d(TAG, "setImage: got new image url: " + imgUrl);
originalImage = ImageManager.getBitmap(imgUrl);
filteredImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
finalImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
imagePreview.setImageBitmap(originalImage);
filtersListFragment.prepareThumbnail(originalImage);
}
}
But getActivity(), getContext(), activity.this, simply this : all are returning null on this line: ThumbnailManager.processThumbs(getContext). Earlier, activity.runOnUiThread() was also returning null but no idea how it got fixed. I have tried some solutions on similar questions but none of them have worked so far. Any lead would be appreciated.
As i mentioned earlier, this error is occurring due to Fragment is not attached to any activity.
private void setImage(){
intent = getIntent();
FiltersListFragment filtersListFragment = new FiltersListFragment();
// it will add filterListFragment into container and container is defined in your current activity xml file where you want to show this fragment
getSupportedFragmentManager().beginTransaction()
.replace(R.id.container, filtersListFragment)
.commit();
if(intent.hasExtra(getString(R.string.selected_image))){
imgUrl = intent.getStringExtra(getString(R.string.selected_image));
Log.d(TAG, "setImage: got new image url: " + imgUrl);
originalImage = ImageManager.getBitmap(imgUrl);
filteredImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
finalImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
imagePreview.setImageBitmap(originalImage);
filtersListFragment.prepareThumbnail(originalImage);
}
}
Some references:
How to use fragment
Adding fragment programmatically
Edit
as viewpager is being used and its different issue then previous so i'm adding other answer here
in your activity you're shadowing fragment object which leads to this error as the new fragment is not attached to activity still you're trying to call it's method. so make following changes to make it working.
private void setImage(){
intent = getIntent();
// we have to remove below line as it create new object of FiltersListFragment instead we use the same object as defined in setupViewPager() method
//FiltersListFragment filtersListFragment = new FiltersListFragment();
....
}

Cannot display images in Html.fromHtml() in text view for images coming dynamically

I am developing an app in android studio in which contents are coming form an Api in a recyclerview. In the api there is an element "content" that sends all html tags with images like a full page. I have to display that page in textview. I have tried Htm.fromHtml method but it is not displaying the images. I have searched all answers and got the solution of ImageGetter method, but I am not able to display dynamic content in the recycleradapter from ImageGetter. I have to keep the images in the drawable of my app and match the source URL that is being parsed. Please help. Below is my code.
PageActivity.java
public class PageActivity extends AppCompatActivity {
RequestQueue queue;
String menuidpage;
RecyclerView recyclerView;
List<MenuFeeds> feedsList = new ArrayList<MenuFeeds>();
String newimage = "http://www.groveus.com/micro/assets/uploads/page/";
PageRecyclerAdapter adapter;
private ProgressDialog pDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_page);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
Bundle bundle=getIntent().getExtras();
menuidpage=bundle.getString("page_id");
recyclerView = (RecyclerView) findViewById(R.id.recyclerviewpage);
pDialog = new ProgressDialog(this);
adapter = new PageRecyclerAdapter(this, feedsList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
//Getting Instance of Volley Request Queue
queue = NetworkController.getInstance(this).getRequestQueue();
//Volley's inbuilt class to make Json array request
pDialog.setMessage("Loding...");
pDialog.show();
String url = "http://www.groveus.com/micro/api/index.php/pages/view?
id="+menuidpage;
JsonArrayRequest menuReq = new JsonArrayRequest(url, new
Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
pDialog.dismiss();
for (int i = 0; i < response.length(); i++) {
try {
JSONObject obj = response.getJSONObject(i);
MenuFeeds feeds = new MenuFeeds(obj.getInt("page_id"),
obj.getString("status"), obj.getString("title"),
newimage+obj.getString("image"),obj.getString("content"));
// adding movie to movies array
feedsList.add(feeds);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
//Notify adapter about data changes
adapter.notifyItemChanged(i);
}
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
System.out.println(error.getMessage());
pDialog.dismiss();
}
});
//Adding JsonArrayRequest to Request Queue
queue.add(menuReq);
}
#Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}
PageRecyclerAdapter.java
public class PageRecyclerAdapter extends
RecyclerView.Adapter<PageRecyclerAdapter.MyViewHolder> implements
View.OnTouchListener
{
private List<MenuFeeds> feedsList;
private Context context;
private LayoutInflater inflater;
public PageRecyclerAdapter(Context context, List<MenuFeeds> feedsList) {
this.context = context;
this.feedsList = feedsList;
inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View rootView = inflater.inflate(R.layout.list_layout5, parent, false);
return new MyViewHolder(rootView);
}
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final MenuFeeds feeds = feedsList.get(position);
//Pass the values of feeds object to Views
//holder.idmenu.setText(feeds.getMenuId());
//holder.title.setText(feeds.getFeedName());
/* holder.description.setText(Html.fromHtml(feeds.getDescription(), 0,
new Html.ImageGetter() {
#Override
public Drawable getDrawable(String s) {
int id;
if
(s.equals("http://www.groveus.com/micro/assets/images/URINARY TRACT
INFECTION 1.png")) {
id = R.drawable.urin1;
}
else if
(s.equals("http://www.groveus.com/micro/assets/images/URINARY TRACT
INFECTION 2.png")) {
id = R.drawable.urin2;
}
else if
(s.equals("http://www.groveus.com/micro/assets/images/SKIN AND SOFT TISSUE
INFECTION 1.png")) {
id = R.drawable.skinsoft1;
}
else if
(s.equals("http://www.groveus.com/micro/assets/images/SKIN AND SOFT TISSUE
INFECTION 2.png")) {
id = R.drawable.skinsoft2;
}
else if
(s.equals("http://groveus.com/micro/assets/images/RESPIRATORY TRACT
INFECTION.png")) {
id = R.drawable.respo;
}
else if (s.equals("http://groveus.com/micro/assets/images/LOCAL
BACTERIAL INFECTIONS.png")) {
id = R.drawable.local;
}
else if
(s.equals("http://groveus.com/micro/assets/images/URINARY TRACT INFECTION
2nd 1.png")) {
id = R.drawable.urine2nd1;
}
else if
(s.equals("http://groveus.com/micro/assets/images/URINARY TRACT INFECTION
2nd 2.png")) {
id = R.drawable.urine2nd2;
}
else if
(s.equals("http://groveus.com/micro/assets/images/table.png")) {
id = R.drawable.table;
}
else if
(s.equals("http://www.groveus.com/micro/assets/images/table 2.png")) {
id = R.drawable.table2;
}
else {
return null;
}
Drawable d = context.getResources().getDrawable(id);
d.setBounds(0,0,1020,600);
return d;
}
}, null));*/
holder.description.setText(Html.fromHtml(feeds.getDescription()));
holder.description.setOnTouchListener(this);
holder.description.setMovementMethod(new ScrollingMovementMethod());
holder.imageview.setImageUrl(feeds.getImgURL(),
NetworkController.getInstance(context).getImageLoader());
// holder.ratingbar.setProgress(feeds.getRating());
}
#Override
public int getItemCount() {
return feedsList.size();
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
view.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView title,description;
private NetworkImageView imageview;
//private ProgressBar ratingbar;
public MyViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.ImageNameTextView);
description = (TextView) itemView.findViewById(R.id.desc);
//idmenu = (TextView) itemView.findViewById(R.id.ImageNameTextView2);
UrlImageParser p=new UrlImageParser(description,context);
// Volley's NetworkImageView which will load Image from URL
imageview = (NetworkImageView) itemView.findViewById(R.id.thumbnail);
}
}
}
MenuFeeds.java
public class MenuFeeds
{
private String imgURL, feedName, description,page;
//private String id;
private int id;
public MenuFeeds(int menuid, String page, String name, String imgurl,String
desc) {
this.id=menuid;
this.page=page;
this.feedName = name;
this.imgURL = imgurl;
this.description = desc;
//this.rating = rating;
}
public int getMenuId() {
return id;
}
public String getPageID()
{
return page;
}
public String getDescription() {
return description;
}
public String getImgURL() {
return imgURL;
}
public String getFeedName() {
return feedName;
}
}
I also faced a similar problem month ago and used this and it works fine :
String htmlData = listData.get(position).getValue();
String showData = htmlData.replace("\n", "");
URLImageParser p = new URLImageParser(holder.textt, context);
Spanned htmlAsSpanned = Html.fromHtml(showData,p,null);
holder.yourTextView.setText(htmlAsSpanned);
Now copy and paste these 2 methods :
First method :
public class URLDrawable extends BitmapDrawable {
protected Drawable drawable;
#Override
public void draw(Canvas canvas) {
if(drawable != null) {
drawable.draw(canvas);
}
}
}
///Second Method :
public class URLImageParser implements Html.ImageGetter {
Context c;
TextView container;
/***
* Construct the URLImageParser which will execute AsyncTask and refresh the container
* #param t
* #param c
*/
public URLImageParser(TextView t, Context c) {
this.c = c;
this.container = t;
}
public Drawable getDrawable(String source) {
URLDrawable urlDrawable = new URLDrawable();
// get the actual source
ImageGetterAsyncTask asyncTask =
new ImageGetterAsyncTask( urlDrawable);
asyncTask.execute(source);
// return reference to URLDrawable where I will change with actual image from
// the src tag
return urlDrawable;
}
public class ImageGetterAsyncTask extends AsyncTask<String, Void, Drawable> {
URLDrawable urlDrawable;
public ImageGetterAsyncTask(URLDrawable d) {
this.urlDrawable = d;
}
#Override
protected Drawable doInBackground(String... params) {
String source = params[0];
return fetchDrawable(source);
}
#Override
protected void onPostExecute(Drawable result) {
// set the correct bound according to the result from HTTP call
urlDrawable.setBounds(0, 0, 0 + result.getIntrinsicWidth(), 0
+ result.getIntrinsicHeight());
// change the reference of the current drawable to the result
// from the HTTP call
urlDrawable.drawable = result;
// redraw the image by invalidating the container
URLImageParser.this.container.invalidate();
URLImageParser.this.container.setHeight((URLImageParser.this.container.getHeight()
+ result.getIntrinsicHeight()));
}
/***
* Get the Drawable from URL
* #param urlString
* #return
*/
public Drawable fetchDrawable(String urlString) {
try {
InputStream is = fetch(urlString);
Drawable drawable = Drawable.createFromStream(is, "src");
drawable.setBounds(0, 0, 0 + drawable.getIntrinsicWidth(), 0
+ drawable.getIntrinsicHeight());
return drawable;
} `enter code here`catch (Exception e) {
return null;
}
}
private InputStream fetch(String urlString) throws MalformedURLException, IOException {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet request = new HttpGet(urlString);
HttpResponse response = httpClient.execute(request);
return response.getEntity().getContent();
}
}
}
try this
load your image using piccaso
add below library in your build.gradle
implementation 'com.squareup.picasso:picasso:2.71828'
when you need to set image use piccaso this way
Picasso.get()
.load(your_image)
.placeholder(R.drawable.user_placeholder)
.error(R.drawable.your_error_image_or_blank)
.into(your_imageView);

Improve Large ListView Adapter smooth scroll, sometimes jerky

I'm trying to see what is making my listview jerk sometimes when scroll, at times it's bad especially when the application first launches.
All the conditions I have are necessary, unless there is something I don't know(highly likely).
I'm not running certain tasks on a seperate thread because they are dependent on the data I receive from the backend(I'm coding both, so backend suggestions are welcome as well). Product is in beta but really need to make this a slightly bit smoother. I'm compressing the images, and they are a bit long but it's not the problem because when I upload the images from the device, I also include the width and height of the image and send that along to the backend. These dimensions come back when loading the list.
One thing I wonder is if calculating/converting the dimensions for the specific device's screen is causing the slight lag. Not sure how resource intensive that task is, but without it(without knowing the dimensions, each row would start out flat and then expand to the actual picture size which would cause the list to jump, so I can't run that calculation on the background either.)
Basically the scrolling isn't bad, but I need to improve this somehow.
Here is my Adapter:
public class VListAdapter extends BaseAdapter {
ViewHolder viewHolder;
private boolean isItFromProfile;
/**
* fields For number formating, ex. 1000
* would return 1k in the format method
*/
private static final NavigableMap<Long, String> suffixes = new TreeMap<>();
static {
suffixes.put(1_000L, "k");
suffixes.put(1_000_000L, "M");
suffixes.put(1_000_000_000L, "G");
suffixes.put(1_000_000_000_000L, "T");
suffixes.put(1_000_000_000_000_000L, "P");
suffixes.put(1_000_000_000_000_000_000L, "E");
}
private Context mContext;
private LayoutInflater mInflater;
private ArrayList<Post> mDataSource;
private static double lat;
private static double lon;
public VListAdapter(Context context, ArrayList<Post> items) {
mContext = context;
mDataSource = items;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = false;
mInflater = LayoutInflater.from(context);
}
public VListAdapter() {
}
public VListAdapter(Context baseContext, ArrayList<Post> posts, boolean b) {
mContext = baseContext;
mDataSource = posts;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = b;
mInflater = LayoutInflater.from(baseContext);
}
public void addElement(Post post) {
mDataSource.add(0, post);
this.notifyDataSetChanged();
}
#Override
public int getCount() {
return mDataSource.size();
}
#Override
public Object getItem(int position) {
return mDataSource.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
int limit = Math.min(position + 4, getCount());
for (int i = position; i < limit; i++) {
Glide.with(mContext).load(((Post) getItem(i)).getFilename().toString()).preload();
}
// StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads()
// .detectDiskWrites().detectNetwork()
// .penaltyLog().build());
View rowView = convertView;
if (rowView == null) {
viewHolder = new ViewHolder();
rowView = mInflater.inflate(R.layout.mylist, parent, false);
viewHolder.titleTextView = (TextView) rowView.findViewById(R.id.usernameinlist);
viewHolder.timeago = (TextView) rowView.findViewById(R.id.timeago);
//viewHolder.sharebutton = (ImageView) rowView.findViewById(R.id.sharebutton);
viewHolder.likesTextView = (TextView) rowView.findViewById(R.id.likestext);
viewHolder.viewcount = (TextView) rowView.findViewById(R.id.viewcount);
viewHolder.distance = (TextView) rowView.findViewById(R.id.distance);
viewHolder.footprints = (TextView) rowView.findViewById(R.id.footprintcount);
viewHolder.postText = (TextView) rowView.findViewById(R.id.posttext);
viewHolder.profilePic = (ImageView) rowView.findViewById(R.id.profilethumb);
viewHolder.caption = (TextView) rowView.findViewById(R.id.captiontext);
viewHolder.moremenu = (ImageView) rowView.findViewById(R.id.dots);
viewHolder.likesPic = (ImageView) rowView.findViewById(R.id.likeimage);
viewHolder.mapitPic = (ImageView) rowView.findViewById(R.id.mapimage);
viewHolder.playbutton = (ImageView) rowView.findViewById(R.id.playbutton);
viewHolder.videoThumb = (ImageView) rowView.findViewById(R.id.videothumb);
viewHolder.listphoto = (ImageView) rowView.findViewById(R.id.listphoto);
viewHolder.rainbow = (ImageView) rowView.findViewById(R.id.rainbow);
rowView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) rowView.getTag();
}
final Post post = (Post) getItem(position);
int color = Color.parseColor("#dddddd");
viewHolder.likesPic.setColorFilter(color);
viewHolder.mapitPic.setColorFilter(color);
viewHolder.moremenu.setColorFilter(color);
if (Hawk.count() == 0)
initHawkWithDataFromServer();
if (isItFromProfile) {
viewHolder.profilePic.setVisibility(View.GONE);
viewHolder.titleTextView.setVisibility(View.GONE);
viewHolder.distance.setVisibility(View.GONE);
}
viewHolder.titleTextView.setText(post.getUsername());
PrettyTime prettyTime = new PrettyTime();
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size() - 1)));
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
if (post.getViews() != null)
viewHolder.viewcount.setText(format(post.getViews()) + (post.getViews() == 1 ? " View" : " Views"));
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername()
+ ".jpg";
String filename = post.getS3link();
final String videoThumbURL = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + filename;
Glide.with(mContext).load(profilePictureS3Url).asBitmap().centerCrop().into(new BitmapImageViewTarget(viewHolder.profilePic) {
#Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(mContext.getResources(), resource);
circularBitmapDrawable.setCircular(true);
viewHolder.profilePic.setImageDrawable(circularBitmapDrawable);
}
});
int height = ((Post) getItem(position)).getHeight();
int width = ((Post) getItem(position)).getWidth();
if (height != 0 && width != 0) {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
} else {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
}
if (post.getType() == null) {
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
//Glide.clear(viewHolder.listphoto);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
viewHolder.playbutton.setVisibility(View.VISIBLE);
}
if (post.getType() != null) {
if (post.getType().equals("video")) {
viewHolder.playbutton.setVisibility(View.VISIBLE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
}
if (post.getType().equals("image")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.listphoto.setVisibility(View.VISIBLE);
viewHolder.listphoto.setBottom(0);
Glide.with(mContext).load(post.getFilename().toString())
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate()
.into(viewHolder.listphoto);
}
if (post.getType().equals("text")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
viewHolder.postText.setVisibility(View.VISIBLE);
viewHolder.postText.setText(post.getText());
}
}
if (Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.clearColorFilter();
Glide.with(mContext).load(R.drawable.heartroundorange).into(viewHolder.likesPic);
((ImageView) viewHolder.likesPic).setColorFilter(Color.parseColor("#ff3a6f"));
} else {
Glide.with(mContext).load(R.drawable.heartroundgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.likesPic);
}
if (Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.clearColorFilter();
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
((ImageView) viewHolder.mapitPic).setColorFilter(Color.parseColor("#444444"));
} else {
Glide.with(mContext).load(R.drawable.dropdarkgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.mapitPic);
}
if (!Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Hawk.put("mapped" + post.getId().get$oid(), 1);
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
viewHolder.footprints.setText(String.valueOf(post.getLocation().size() + 1));
post.getLocation().add(new double[]{PostListFragment.lon, PostListFragment.lat});
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postMappedToServer(post.getId().get$oid());
}
});
t.start();
TastyToast.makeText(mContext, "Post dropped off here.", TastyToast.LENGTH_SHORT, TastyToast.CONFUSING);
}
});
} else {
viewHolder.mapitPic.setClickable(false);
}
if (!Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Hawk.put("liked" + post.getId().get$oid(), 1);
viewHolder.likesPic.setClickable(false);
((ImageView) viewHolder.likesPic).setImageResource(R.drawable.heartroundorange);
viewHolder.likesTextView.setText(String.valueOf(post.getLikes() + 1));
post.setLikes(post.getLikes() + 1);
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postLikeToServer(post);
}
});
t.start();
}
});
} else {
viewHolder.likesPic.setClickable(false);
}
if (post.getType() == null || post.getType().equals("video"))
viewHolder.videoThumb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (VListAdapter.this.mContext instanceof ProfileFeed) {
((ProfileFeed) VListAdapter.this.mContext).closeActivity();
}
Intent broadcast = new Intent();
broadcast.setAction("com.molehead.openout.POST");
broadcast.putExtra("postId", post.getFilename().toString());
broadcast.putExtra("hawkId", post.getId().get$oid());
broadcast.putExtra("s3link", post.getS3link());
broadcast.putExtra("username", post.getUsername());
if (Hawk.contains("liked" + post.getId().get$oid()))
broadcast.putExtra("liked", "yes");
else
broadcast.putExtra("liked", "no");
broadcast.putExtra("likecount", post.getLikes().toString());
App.post = post;
LocalBroadcastManager.getInstance(mContext.getApplicationContext()).sendBroadcast(broadcast);
}
});
viewHolder.moremenu.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
PopupMenu popup = new PopupMenu(mContext.getApplicationContext(), viewHolder.moremenu, Gravity.CENTER);
//Inflating the Popup using xml file
popup.getMenuInflater().inflate(R.menu.menu_main, popup.getMenu());
//registering popup with OnMenuItemClickListener
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_share:
String postId = post.getId().get$oid();
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
String shareBody = postId + ".jpg"; //https://openout.herokuapp.com/posts/" + postId;
String shareSub = "Shared via Molehead";
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, shareSub);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
sharingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent new_intent = Intent.createChooser(sharingIntent, "Share");
new_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.getApplicationContext().startActivity(new_intent);
break;
}
return true;
}
});
popup.show();
}
});
return rowView;
}
private void initHawkWithDataFromServer() {
SharedPreferences settings = mContext.getApplicationContext().getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
final Call<List<Post>> call = loginService.getLikes(username);
Log.i("lonlat", String.valueOf(lon) + " and " + String.valueOf(lat));
call.enqueue(new Callback<List<Post>>() {
#Override
public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
ArrayList<Post> posts = new ArrayList<>();
posts = (ArrayList<Post>) response.body();
if (!posts.isEmpty())
for (Post p : posts) {
Hawk.put("liked" + p.getId().get$oid(), 1);
}
}
#Override
public void onFailure(Call<List<Post>> call, Throwable t) {
}
});
}
private void postMappedToServer(String oid) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Log.i("postlistfraglat", String.valueOf(PostListFragment.lat));
Call<ResponseBody> call = loginService.addLocation(oid, PostListFragment.lon, PostListFragment.lat);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful())
Log.i("mapped", "success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
public void postLikeToServer(Post post) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Call<ResponseBody> call = loginService.like(post, 1, username);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
try {
Log.i("call", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i("MFEED", "like request failed");
}
});
}
public static String format(long value) {
//Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
if (value < 0) return "-" + format(-value);
if (value < 1000) return Long.toString(value); //deal with easy case
Map.Entry<Long, String> e = suffixes.floorEntry(value);
Long divideBy = e.getKey();
String suffix = e.getValue();
long truncated = value / (divideBy / 10); //the number part of the output times 10
boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}
static class ViewHolder {
private TextView titleTextView;
private TextView timeago;
private TextView likesTextView;
private TextView viewcount;
private TextView distance;
private TextView footprints;
private ImageView profilePic;
private ImageView moremenu;
private ImageView likesPic;
private ImageView mapitPic;
private ImageView rainbow;
//private ImageView sharebutton;
private TextView caption;
private ImageView listphoto;
private ImageView videoThumb;
private ImageView playbutton;
private TextView postText;
private Post post;
}
private float getHeight(float height, float width) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return (height * size.x / width);
}
}
It is impossible to point to a specific issue because there is so much code in your Adapter. One thing is sure, though - switching to RecyclerView won't help you in this case.
Adapters should not contain business logic - they should only "adapt" input objects to the underlying Views. In your case, it seems like the adapter performs calculations, spawns new threads, performs network requests, etc.
You need to refactor your code such that the adapter will be similar to this:
public class PostsListAdapter extends ArrayAdapter<Post> {
private Context mContext;
public PostsListAdapter(Context context, int resource) {
super(context, resource);
mContext = context;
}
public void bindPosts(List<Post> posts) {
clear();
addAll(posts);
notifyDataSetChanged();
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// assign new View to convertView
// create new ViewHolder
// set ViewHolder as tag of convertView
// set listeners
} else {
// get a reference to existing ViewHolder
}
// populate ViewHolder's elements with data from getItem(position)
// kick off asynchronous loading of images
// NOTE: no calculations allowed here - just simple bidding of data to Views
return convertView;
}
}
Your code needs to be structured in such a way, that business logic that involves calculations and transformation of data executed before you bind a new data to ListView, and Post objects that you pass to bindPosts() method already contain the results of the aforementioned calculations and transformations.
Adapter just "adapts" the final data from Posts to Views - nothing more.
If you're short on time now, and just need to "make it work", then I would start by removing the logic that spawns new threads and makes network requests. See if this improves performance.
Change your implementation to RecyclerView which is more efficient in terms of scrapping views or recycling.
We can also enable optimizations if the items are static and will not change for significantly smoother scrolling:
recyclerView.setHasFixedSize(true);
Create an intent service and register BroadcastReceiver as data return callback or error callback when api request, business rule, data modification completed. Use synchronous call to execute initHawkWithDataFromServer() in advance and after getting result from api continue modifying or applying business logic. After that create new adapter or update existing adapter data set.
Move all the below data calculation or data value formatting logic from adapter's getView() to above intent service.
You can add more getter and setter to existing Post pojo.
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size)) - 1)));
Post{
//Your existing property
#Expose(serialize = false, deserialize = false)
//equals neither serialize nor deserialize or
private DateTime uploadedDateTime;
//etc. prettyTime.format, String.valueOf
}
Removes unnecessary reflection:
GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
Gson gson = builder.create();
new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).build();
and it to your retrofit Service creation class.
You can also use transient(private transient DateTime uploadedDateTime;)
Remove
public void addElement(Post post) { mDataSource.add(0, post);
this.notifyDataSetChanged();}
and whenever you need to notify if a single or more items inserted, deleted etc. Use the below:
notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
We can use these from the activity or fragment:
//Add a new contact
items.add(0, new Post("Barney"));
//Notify the adapter that an item was inserted at position 0
adapter.notifyItemInserted(0);
Above methods are more efficient. Every time we want to add or remove items from the RecyclerView, we will need to explicitly inform to the adapter of the event. Unlike the ListView adapter, a RecyclerView adapter should not rely on notifyDataSetChanged() since the more granular actions should be used. See the API documentation for more details
Also, if you are intending to update an existing list, make sure to get the current count of items before making any changes. For instance, a getItemCount() on the adapter should be called to record the first index that will be changed.
// record this value before making any changes to the existing list
int curSize = adapter.getItemCount(); // replace this line with wherever you get new records
ArrayList<Post> newItems = Post.createPostsList(20);
// update the existing list
items.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
adapter.notifyItemRangeInserted(curSize, newItems.size());
Diffing Larger Changes
A new DiffUtil class has been added in the v24.2.0 of the support library to help compute the difference between the old and new list. Details
Don't preload images via glide if your image sizes are different. Try creating your own. Also try looking at.
Create color as the class member
int color = Color.parseColor("#dddddd");
Write View.GONE or View.VISIBLE in Post pojo itself, which will be executed in background thread from Retrofit if IntentService. Try api return boolean in json instead "0" as String.
Move all below to IntentService
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
All String concatenation also in Post or IntnetService like:
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername() + ".jpg";
Also you can create color filter in advance and one time only. Remove scrollbar from listview as it calculates height to show scroll bar.
Too many things to improve here. Here is some examples.
I see this
if (Hawk.count() == 0)
initHawkWithDataFromServer();
I believe that the method initHawkWithDataFromServer will be called many times during the time the list appears.
This call can be done only once when the activity was created.
Glide.with(mContext).load(videoThumbURL).fitCenter()
But you should refactor your code first, moving the logic to another class. Try to remove some code like this (it should be done by using some layout attribites)
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);

Android : Bitmaps transmitted as String causing out of memory errors

I am working on an Android project in which I am loading images from our server while loading other information about restaurants. The problem is, it is taking a lot of time to load and whenever I try to load the information again or move the mobile from landscape to potrait or vice-versa, I get out of memory errors.
As I checked on net, the problem users say is because of bitmaps. On the server-side, I am converting the PNG's to Strings and transmitting them, and the String is then converted to bitmap and then displayed. Why is the whole loading time around 4-5 seconds and crashes. Can anyone help me.
Android code :
RestaurantList Activity :
public class getListOfRestaurantsForUser extends AsyncTask<Double,Void,ResponseEntity<RestRestaurant[]>>{
RestaurantList restaurantList = null;
getListOfRestaurantsForUser(RestaurantList restaurantList){
this.restaurantList = restaurantList;
}
#Override
protected ResponseEntity<RestRestaurant[]> doInBackground(Double... doubles) {
double longitude = doubles[0];
double latitude = doubles[1];
Log.d("Longitude",String.valueOf(longitude));
final RestTemplate restTemplate = StaticRestTemplate.getRest();
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", "JSESSIONID=" + StaticRestTemplate.jsessionid);
requestHeaders.setAccept(Collections.singletonList(new MediaType("application", "json")));
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
return restTemplate.exchange(restaurantListURL+longitude+"/"+latitude, HttpMethod.GET, requestEntity, RestRestaurant[].class);
}
#Override
protected void onPostExecute(ResponseEntity<RestRestaurant[]> responseEntity){
RestRestaurant[] restRestaurantList = responseEntity.getBody();
Collections.addAll(restosAsList, restRestaurantList);
ArrayList<HashMap<String, String>> restaurantsArrayHashList = new ArrayList<>();
for(RestRestaurant restRestaurant : restosAsList){
HashMap<String, String> restDisplay = new HashMap<>();
restDisplay.put(restaurantid, String.valueOf(restRestaurant.getRestaurantId()));
restDisplay.put(restaurantName, restRestaurant.getRestaurantName());
restDisplay.put(restaurantDistance, String.valueOf(restRestaurant.getDistanceFromUser()));
restDisplay.put(restaurantDetails,restRestaurant.getRestaurantDetails());
restDisplay.put(restoProfilePicture,restRestaurant.getProfilePicture());
restaurantsArrayHashList.add(restDisplay);
}
listView = (ListView) findViewById(R.id.restosList);
restaurantListAdapter = new RestaurantListAdapter(restaurantList, restaurantsArrayHashList);
listView.setAdapter(restaurantListAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
int restaurantId = restosAsList.get(position).getRestaurantId();
Intent intent = new Intent(RestaurantList.this, MenuCardList.class);
intent.putExtra("restaurantid", restaurantId);
startActivity(intent);
finish();
}
});
}
}
RestaurantList adapter :
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (convertView == null) {
view = inflater.inflate(R.layout.individual_restaurant_detail, null);
TextView restaurantName = (TextView) view.findViewById(R.id.resturantName);
TextView distanceFromUser = (TextView) view.findViewById(R.id.distanceFromUser);
TextView restaurantDetails = (TextView) view.findViewById(R.id.restaurantDetails);
ImageView restoImage = (ImageView) view.findViewById(R.id.resturantImage);
HashMap<String, String> restaurantList;
restaurantList = data.get(position);
restaurantName.setText(restaurantList.get(RestaurantList.restaurantName));
restaurantName.setTypeface(Typeface.DEFAULT_BOLD);
restaurantName.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15);
double distance = Double.valueOf(restaurantList.get(RestaurantList.restaurantDistance));
if(distance < 1000) {
distanceFromUser.setText("Entfernung " + restaurantList.get(RestaurantList.restaurantDistance)+"m");
}else {
distanceFromUser.setText("Entfernung " + String.valueOf(distance/1000) +"km");
}
restaurantDetails.setText(restaurantList.get(RestaurantList.restaurantDetails));
restoImage.setImageBitmap(convertByteArrayToBitmap(restaurantList.get(RestaurantList.restoProfilePicture)));
}
return view;
} #Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (convertView == null) {
view = inflater.inflate(R.layout.individual_restaurant_detail, null);
TextView restaurantName = (TextView) view.findViewById(R.id.resturantName);
TextView distanceFromUser = (TextView) view.findViewById(R.id.distanceFromUser);
TextView restaurantDetails = (TextView) view.findViewById(R.id.restaurantDetails);
ImageView restoImage = (ImageView) view.findViewById(R.id.resturantImage);
HashMap<String, String> restaurantList;
restaurantList = data.get(position);
restaurantName.setText(restaurantList.get(RestaurantList.restaurantName));
restaurantName.setTypeface(Typeface.DEFAULT_BOLD);
restaurantName.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15);
double distance = Double.valueOf(restaurantList.get(RestaurantList.restaurantDistance));
if(distance < 1000) {
distanceFromUser.setText("Entfernung " + restaurantList.get(RestaurantList.restaurantDistance)+"m");
}else {
distanceFromUser.setText("Entfernung " + String.valueOf(distance/1000) +"km");
}
restaurantDetails.setText(restaurantList.get(RestaurantList.restaurantDetails));
restoImage.setImageBitmap(convertByteArrayToBitmap(restaurantList.get(RestaurantList.restoProfilePicture)));
}
return view;
}
private Bitmap convertByteArrayToBitmap(String string){
try {
byte [] encodeByte= Base64.decode(string, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);
} catch (Exception ignored){}
return null;
}
Server side code :
#Override
public List<Restaurant> getNearbyRestaurants(double longitude, double latitude) {
BASE64Encoder base64Encoder = new BASE64Encoder();
final int R = 6371; // Radius of the earth
List<Restaurant> restaurantList = this.listRestaurants();
List<Restaurant> nearbyRestaurantList = new ArrayList<>();
for(Restaurant restaurant : restaurantList){
Double latDistance = toRad(latitude-restaurant.getLatitude());
Double lonDistance = toRad(longitude-restaurant.getLongitude());
Double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) +
Math.cos(toRad(latitude)) * Math.cos(toRad(restaurant.getLatitude())) *
Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
Double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
Double distance = R * c;
restaurant.setDistanceFromUser(distance);
if(distance < 10){
restaurant.setDistanceFromUser((restaurant.getDistanceFromUser()*1000));
nearbyRestaurantList.add(restaurant);
}
String restaurantIdentifierImageString = this.restaurantImageService.getProfileIdentifierForRestaurant(restaurant.getRestaurantId());
if(!(restaurantIdentifierImageString == null)){
try {
try {
File imagePath = new File(restaurantImagePath + restaurantIdentifierImageString +".png");
BufferedImage bufferedImage = ImageIO.read(imagePath);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
restaurant.setProfilePicture( base64Encoder.encode(byteArrayOutputStream.toByteArray()));
} catch (Exception e) {
File imagePath = new File(defaultProfilePath);
BufferedImage bufferedImage = ImageIO.read(imagePath);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
restaurant.setProfilePicture(base64Encoder.encode(byteArrayOutputStream.toByteArray()));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
Collections.sort(nearbyRestaurantList, new Comparator<Restaurant>() {
#Override
public int compare(Restaurant o1, Restaurant o2) {
if(o1.getDistanceFromUser() > o2.getDistanceFromUser()){
return 1;
}
if(o1.getDistanceFromUser() < o2.getDistanceFromUser()){
return -1;
}
return 0;
}
});
return nearbyRestaurantList;
}
Error log :
Caused by: java.lang.OutOfMemoryError: Failed to allocate a 13176356 byte allocation with 370616 free bytes and 361KB until OOM
at java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:83)
at java.lang.StringBuilder.<init>(StringBuilder.java:67)
at com.fasterxml.jackson.core.util.TextBuffer.contentsAsString(TextBuffer.java:346)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._finishAndReturnString(UTF8StreamJsonParser.java:2412)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.getText(UTF8StreamJsonParser.java:285)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:32)
If any more information is required, kindly let me know. Thank you.
Update
Image retrieval method :
#RequestMapping(value = "/restaurantimage/{identifier}")
public HttpEntity<byte[]> getPhoto(#PathVariable String identifier) {
boolean flag = false;
try {
byte[] image;
try {
image = org.apache.commons.io.FileUtils.readFileToByteArray(new File(restaurantImagePath + identifier +".png"));
} catch (FileNotFoundException e) {
flag = true;
image = org.apache.commons.io.FileUtils.readFileToByteArray(new File(defaultProfilePath));
e.printStackTrace();
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
headers.setContentLength(image.length);
return new HttpEntity<>(image, headers);
} catch (IOException ignored) {
}
return null;
}
I don't know what is the requirement at your server side, but for Mobile applications, the memory allocated to the application is very limited. The bitmap which you maybe sending must be of some kilobytes, when it is received, you store that in String or byte array and then write it up in a String.
This consumes a lot of memory of your application and also can create performance issues.
As an alternate approach for it, you can just give the url of the image from your server in a json string and then pass that url to the method of some Imagehelper/Lazyloading library like Volley.
I was finally able to solve the problem by loading the image from Picaso and just sending the URL of the image from the server-side.
Code :
Picasso.with(view.getContext())
Picasso.load(StaticRestTemplate.baseURL+"restaurantimage/"+restaurantList.get(RestaurantList.restoProfilePicture))
.config(Bitmap.Config.RGB_565)
.fit()
.centerInside()
.into(restoImage);

Loading images from JSON as you scroll in Android

I am creating an app with many products being loaded from a JSON file. Everything is working fine, but I have a small problem and I am having difficulty finding an answer to it. On a click of a button, I get all data from JSON file located on a server (it contains a URL value to get the picture of the product). When the button is clicked I would like to move to a list of products straight away and only load visible images. Instead it takes about 8 seconds to load and it loads all images before hand. Could somebody please tell me what am I doing wrong? I read online I should use something like so: imageLoader.DisplayImage(data[position], image);, but I am using a Hashmap, not just a string.
Here is my code, any help would be greatly appreciated:
LazyAdapter
public class LazyAdapter extends BaseAdapter {
private Activity activity;
private ArrayList<HashMap<String, String>> data;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
public LazyAdapter(Activity a, ArrayList<HashMap<String, String>> d) {
activity = a;
data=d;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return data.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.list_row, null);
TextView brand = (TextView)vi.findViewById(R.id.brand); // title
TextView name = (TextView)vi.findViewById(R.id.name); // artist name
TextView price = (TextView)vi.findViewById(R.id.price); // duration
TextView imgValue = (TextView)vi.findViewById(R.id.imgValue);
//TextView rating = (TextView)vi.findViewById(R.id.rating); // rating
ImageView thumb_image=(ImageView)vi.findViewById(R.id.list_image); // thumb image
HashMap<String, String> products = new HashMap<String, String>();
products = data.get(position);
// Setting all values in listview
brand.setText(products.get(CustomizedListView.TAG_BRAND));
name.setText(products.get(CustomizedListView.TAG_NAME));
price.setText("£" + products.get(CustomizedListView.TAG_MIN_GBP));
imgValue.setText(products.get(CustomizedListView.TAG_IMG));
imgValue.setVisibility(View.GONE);
//rating.setText("Rating: " + song.get(CustomizedListView.TAG_RATING_NO) + "/5");
imageLoader.DisplayImage("http://debenhams.scene7.com/is/image/Debenhams/" + imgValue.getText() + "?hei=440&op_usm=1.1,0.5,0,0", thumb_image);
return vi;
}
}
ListView
public class CustomizedListView extends Activity {
// All static variables
static final String link = "https://dl.dropboxusercontent.com/u/142838353/productlist.json";
// XML node keys
static final String TAG_PRODUCTS = "Products";
static final String TAG_PRODNUM = "prodnum";
static final String TAG_BRAND = "brand";
static final String TAG_NAME = "name"; // parent node
static final String TAG_MIN_GBP = "minGBP";
static final String TAG_IMG = "img";
static final String TAG_RATING_NO = "rating_number";
JSONArray products = null;
ListView list;
LazyAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.shop);
// Hashmap for ListView
ArrayList<HashMap<String, String>> productList = new ArrayList<HashMap<String, String>>();
// Creating JSON Parser instance
JSONParser jParser = new JSONParser();
// getting JSON string from URL
JSONObject json = jParser.getJSONFromUrl(link);
try {
// Getting Array of Contacts
products = json.getJSONArray(TAG_PRODUCTS);
// looping through All Contacts
for(int i = 0; i < products.length(); i++){
JSONObject c = products.getJSONObject(i);
// Storing each json item in variable
// String prodnum = c.getString(TAG_PRODNUM);
String brand = c.getString(TAG_BRAND);
String name = c.getString(TAG_NAME);
String img = c.getString(TAG_IMG);
String price = c.getString(TAG_MIN_GBP);
//String ratingNo = c.getString(TAG_RATING_NO);
// creating new HashMap
HashMap<String, String> map = new HashMap<String, String>();
// adding each child node to HashMap key => value
map.put(TAG_MIN_GBP, price);
map.put(TAG_BRAND, brand);
map.put(TAG_NAME, name);
map.put(TAG_IMG, img);
//map.put(TAG_RATING_NO, ratingNo);
// adding HashList to ArrayList
productList.add(map);
}
} catch (JSONException e) {
e.printStackTrace();
}
list=(ListView)findViewById(R.id.list);
// Getting adapter by passing xml data ArrayList
adapter=new LazyAdapter(this, productList);
list.setAdapter(adapter);
// Click event for single list row
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
String brand = ((TextView) view.findViewById(R.id.brand)).getText().toString();
String name = ((TextView) view.findViewById(R.id.name)).getText().toString();
String imgValue = ((TextView) view.findViewById(R.id.imgValue)).getText().toString();
String price = ((TextView) view.findViewById(R.id.price)).getText().toString();
//String imgVal = ((ImageView) view.findViewById(R.id.list_image)).;
//String imgVal = ((ImageView) view.findViewById(R.id.img)).getResources().toString();
// Starting new intent
Intent in = new Intent(getApplicationContext(), ProductActivity.class);
in.putExtra(TAG_BRAND, brand);
in.putExtra(TAG_NAME, name);
in.putExtra(TAG_IMG, imgValue);
in.putExtra(TAG_MIN_GBP, price);
startActivity(in);
}
});
}
#Override
public void onDestroy()
{
list.setAdapter(null);
super.onDestroy();
}
}
ImageLoader
public class ImageLoader {
MemoryCache memoryCache=new MemoryCache();
FileCache fileCache;
private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
ExecutorService executorService;
Handler handler=new Handler();//handler to display images in UI thread
public ImageLoader(Context context){
fileCache=new FileCache(context);
executorService=Executors.newFixedThreadPool(5);
}
final int stub_id=R.drawable.default_image;
public void DisplayImage(String url, ImageView imageView)
{
imageViews.put(imageView, url);
Bitmap bitmap=memoryCache.get(url);
if(bitmap!=null)
imageView.setImageBitmap(bitmap);
else
{
queuePhoto(url, imageView);
imageView.setImageResource(stub_id);
}
}
private void queuePhoto(String url, ImageView imageView)
{
PhotoToLoad p=new PhotoToLoad(url, imageView);
executorService.submit(new PhotosLoader(p));
}
private Bitmap getBitmap(String url)
{
File f=fileCache.getFile(url);
//from SD cache
Bitmap b = decodeFile(f);
if(b!=null)
return b;
//from web
try {
Bitmap bitmap=null;
URL imageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
InputStream is=conn.getInputStream();
OutputStream os = new FileOutputStream(f);
Utils.CopyStream(is, os);
os.close();
conn.disconnect();
bitmap = decodeFile(f);
return bitmap;
} catch (Throwable ex){
ex.printStackTrace();
if(ex instanceof OutOfMemoryError)
memoryCache.clear();
return null;
}
}
//decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f){
try {
//decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream stream1=new FileInputStream(f);
BitmapFactory.decodeStream(stream1,null,o);
stream1.close();
//Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE=256;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true){
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}
//decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
FileInputStream stream2=new FileInputStream(f);
Bitmap bitmap=BitmapFactory.decodeStream(stream2, null, o2);
stream2.close();
return bitmap;
} catch (FileNotFoundException e) {
}
catch (IOException e) {
e.printStackTrace();
}
return null;
}
//Task for the queue
private class PhotoToLoad
{
public String url;
public ImageView imageView;
public PhotoToLoad(String u, ImageView i){
url=u;
imageView=i;
}
}
class PhotosLoader implements Runnable {
PhotoToLoad photoToLoad;
PhotosLoader(PhotoToLoad photoToLoad){
this.photoToLoad=photoToLoad;
}
#Override
public void run() {
try{
if(imageViewReused(photoToLoad))
return;
Bitmap bmp=getBitmap(photoToLoad.url);
memoryCache.put(photoToLoad.url, bmp);
if(imageViewReused(photoToLoad))
return;
BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad);
handler.post(bd);
}catch(Throwable th){
th.printStackTrace();
}
}
}
boolean imageViewReused(PhotoToLoad photoToLoad){
String tag=imageViews.get(photoToLoad.imageView);
if(tag==null || !tag.equals(photoToLoad.url))
return true;
return false;
}
//Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable
{
Bitmap bitmap;
PhotoToLoad photoToLoad;
public BitmapDisplayer(Bitmap b, PhotoToLoad p){bitmap=b;photoToLoad=p;}
public void run()
{
if(imageViewReused(photoToLoad))
return;
if(bitmap!=null)
photoToLoad.imageView.setImageBitmap(bitmap);
else
photoToLoad.imageView.setImageResource(stub_id);
}
}
public void clearCache() {
memoryCache.clear();
fileCache.clear();
}
}
If you're not averse to using a library to do it, I had a lot of success with Universal Image Loader. It takes a lot of the complexity of the problem away and is pretty easy to get set up. There's some discussion of the problem from a while back with a few other implementations (I'm not sure if any of them are maintained though). Even if you don't use the library, their design patterns might be a useful starting point.

Categories

Resources