We are Borg
We are Borg

Reputation: 5313

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;
}

Upvotes: 1

Views: 79

Answers (2)

We are Borg
We are Borg

Reputation: 5313

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);

Upvotes: 0

user4571931
user4571931

Reputation:

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.

Upvotes: 2

Related Questions