2

I have a gridview that contains an array of images in my drawable folder. I have it worked out right now to send the drawable to another activity where the user will view the image before setting a picture from the raw folder as the wallpaper. I can't use the drawable asset because of compression and a suitable image cause a crash from a lack of memory.

My MainActivity file with the gridview:

GridView androidGridView;

private Integer asset1 = R.drawable.asset1;
private Integer asset2 = R.drawable.asset2;
private Integer asset3 = R.drawable.asset1;
private Integer asset4 = R.drawable.asset2;
private Integer asset5 = R.drawable.asset1;
private Integer asset6 = R.drawable.asset2;
private Integer[] images = {
        asset1, asset2, asset3,
        asset4, asset5, asset6
};

Integer[] imagesIDs = {
        R.raw.asset1, R.raw.asset2, R.drawable.asset1,
        R.drawable.asset1, R.drawable.asset1, R.drawable.asset1,
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    androidGridView = findViewById(R.id.gridview_android_example);
    androidGridView.setAdapter(new ImageAdapterGridView(this));

    androidGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent,
                                View v, int position, long id) {
            int imageRes = images[position];

            Intent intent = new Intent(MainActivity.this, ViewActivity.class);
            intent.putExtra("IMAGE_RES", imageRes);
            startActivity(intent);
        }
    });

}

public class ImageAdapterGridView extends BaseAdapter {
    private Context mContext;

    public ImageAdapterGridView(Context c) {
        mContext = c;
    }

    public int getCount() {
        return images.length;
    }

    public Object getItem(int position) {
        return null;
    }

    public long getItemId(int position) {
        return 0;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView mImageView;

        if (convertView == null) {
            mImageView = new ImageView(mContext);
            mImageView.setLayoutParams(new GridView.LayoutParams(525, 350));
            mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
            mImageView.setPadding(16, 16, 16, 16);
        } else {
            mImageView = (ImageView) convertView;
        }
        mImageView.setImageResource(images[position]);
        return mImageView;
    }

My ViewActivity file where the user will preview the image before setting it as the wallpaper:

private Integer asset1 = R.raw.asset1;
private Integer asset2 = R.raw.asset2;
private Integer asset3 = R.raw.asset1;
private Integer asset4 = R.raw.asset2;
private Integer asset5 = R.raw.asset1;
private Integer asset6 = R.raw.asset2;
private Integer[] images = {
        asset1, asset2, asset3,
        asset4, asset5, asset6
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_view);

    Bundle extras = getIntent().getExtras();
    int imageRes = extras.getInt("IMAGE_RES");

    ImageView preview = findViewById(R.id.preview);
    preview.setImageResource(imageRes);
    preview.setScaleType(ImageView.ScaleType.CENTER_CROP);

    Button set = findViewById(R.id.setButton);
    set.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

        }
    });
}

I'm not sure whether or not I'm on the right track, but if anyone can point me in the right direction that would be great!

1 Answers1

1

A lot has been written on SO about Out of Memory errors when working with bitmaps and images in android apps: here, here and here, for example.

For the special purpose of setting the wallpaper on a device, you might try this sort of approach. I don't guarantee that you'll always avoid OOM errors doing it this way, but it should prevent most of them.

It does that by trying to stay within the app's current free memory when it decodes the resource into a bitmap. It also recycles the bitmap at the end.

One advantage is that you don't have to come up with the required width and height of the output bitmap. It does that for you, based on free memory. (That's also a disadvantage -- you're not free to choose whatever bitmap dimensions you want. They might be too large and cause a crash.)

It can take some time to do the decoding, which is why it's done on a background thread.

Anyway, this works for me:

Add an ExecutorService and the method decodeBitmapWithinFreeMemory to your ViewActivity:

private ExecutorService executor = Executors.newSingleThreadExecutor();

...

// adapted from https://developer.android.com/topic/performance/graphics/load-bitmap.html
private Bitmap decodeResourceWithinFreeMemory(Resources resources, int resourceId, float requiredAspectRatio) {

    // get just the size of the resource image
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(resources, resourceId, options);

    // estimate number of pixels we can work with in current free memory   
    long freeMem = Runtime.getRuntime().freeMemory();
    long spaceForARGV8888Px = freeMem / 4; // est. number of ARGV_8888 pixels that can be stored

    // calculate the sides of a rectangle with approximately that number of pixels
    long squareRootLowerBound = (long) Math.floor(Math.pow(spaceForARGV8888Px, 0.5));
    int requestedWidth = (int) Math.floor(squareRootLowerBound * requiredAspectRatio);
    int requestedHeight = (int) Math.floor(squareRootLowerBound / requiredAspectRatio);

    // find the right sample size by aggressively increasing sampleSize var: require only that
    // _one_ of the output dimensions be greater than the corresponding requested dimension
    int sampleSize = 1;
    while ((options.outHeight / (2 * sampleSize) ) >= requestedHeight
            || (options.outWidth / (2 * sampleSize) ) >= requestedWidth) {
        sampleSize *= 2;
    }

    // output the bitmap by sampling the input resource at the calculated sampleSize
    options.inSampleSize = sampleSize;
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(resources, resourceId, options);
}

Invoke decodeBitmapWithinFreeMemory inside the button's onClick method, feeding it the device's screen aspect ratio:

DisplayMetrics metrics = getResources().getDisplayMetrics();
final float screenAspectRatio = (float)metrics.widthPixels/(float)metrics.heightPixels;
executor.submit(new Runnable() {
    @Override
    public void run() {
        try {
            Bitmap drawableAsBitmap = decodeResourceWithinFreeMemory(getResources(),
                    R.raw.asset1, screenAspectRatio);
            WallpaperManager.getInstance(MainActivity.this).setBitmap(drawableAsBitmap);
            drawableAsBitmap.recycle();
        } catch (IOException ioe) {
            Log.e(TAG, "Could not set wallpaper to bitmap", ioe);
        }
    }
});

Also note that you can optionally add a BroadcastReceiver to your Activities to be notified that the wallpaper has been set. (See the documentation for setBitmap.)

albert c braun
  • 2,650
  • 1
  • 22
  • 32