I have a CountDownTimer which calls the ChangeWallpaper() method every 15 seconds. The Wallpaper changes as it should but when I try and open the App, it makes the App Drawer screen unresponsive for few seconds. When the App finally opens, everything that I select takes 5-10 seconds to respond. I read about AsyncTask in Android Developer which is supposed to load the Bitmaps outside the UI thread and prevent the App from hanging, but it doesn't seem to be working.
The following code is inside my Activity class:
/** changeWallpaper() **/ - called by CountDownTimer every 15 seconds
protected void changeWallpaper() throws IOException {
Integer totalimages = finallist.size();
if (lastloopcount == totalimages) { // if end of the list of images is reached, it resets and goes back to top.
loopcount = 0;
lastloopcount = 0;
}
for (String imagepath : finallist) { // "finallist" is global variable with all the image's paths in an array list. The Loop count is to select the next image in the array list every 15 seconds.
loopcount++;
if (loopcount > lastloopcount) {
lastloopcount = loopcount;
loopcount = 0;
WallpaperManager wm = WallpaperManager.getInstance(this);
wm.setBitmap(decodeImageFromPath(imagepath));
break;
}
}
}
/** AsyncTask Wallpaper Load **/
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
public BitmapWorkerTask(ImageView imageView) {
new WeakReference<ImageView>(imageView);
}
#Override
protected Bitmap doInBackground(Integer... params) {
return null;
}
}
/** decodeImageFromPath() **/
public Bitmap decodeImageFromPath(String imagepath) {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels << 2;
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagepath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(imagepath, options);
}
/** WallpaperManager (Method) **/
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// ... Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int stretch_width = Math.round((float)width / (float)reqWidth);
int stretch_height = Math.round((float)height / (float)reqHeight);
if (stretch_width <= stretch_height) return stretch_height;
else return stretch_width;
}
Have I used the AsyncTask function correctly?
Is there an easier way to write this?
Thanks in advance.
EDIT:
/** Spinner **/
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String chosenTime = parent.getItemAtPosition(position).toString();
int chosenTimeNew = 0;
if (chosenTime.contains("sec")) {
chosenTime = chosenTime.replace(" sec","");
chosenTimeNew = Integer.parseInt(chosenTime) * 500;
} else if (chosenTime.contains("min") ) {
chosenTime = chosenTime.replace(" min","");
chosenTimeNew = Integer.parseInt(chosenTime) * 30000;
} else if (chosenTime.contains("hour")) {
chosenTime = chosenTime.replace(" hour","");
chosenTimeNew = (Integer.parseInt(chosenTime) * 30000) * 60;
} else if (chosenTime.contains("day")) {
chosenTime = chosenTime.replace(" day","");
chosenTimeNew = ((Integer.parseInt(chosenTime) * 30000) * 60) * 24;
}
rSpeed = chosenTimeNew;
}
EDIT 2:
Called by CountDownTimer():
new BitmapWorkerTask(null).execute(imagepath);
Then:
/** AsyncTask Wallpaper Load **/
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
public BitmapWorkerTask(ImageView imageView) {
new WeakReference<ImageView>(imageView);
}
#Override
protected Bitmap doInBackground(String... params) {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels << 2;
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(params[0], options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(params[0], options);
return bmp;
}
protected void onPostExecute(Bitmap bmp) {
Context context = getApplicationContext();
WallpaperManager wm = WallpaperManager.getInstance(context);
try {
wm.setBitmap(bmp);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/** WallpaperManager (Method) **/
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// ... Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int stretch_width = Math.round((float)width / (float)reqWidth);
int stretch_height = Math.round((float)height / (float)reqHeight);
if (stretch_width <= stretch_height) return stretch_height;
else return stretch_width;
}
Your main code(processing the bitmap) should be called from within the doInBackground method. Else it's the same as a synchronous call here.
#Override
protected Bitmap doInBackground(String... params) {
Bitmap bmp = decodeImageFromPath(params[0]);
return bmp;
}
protected void onPostExecute(Bitmap bmp) {
wm.setBitmap(bmp)
}
new BitmapWorkerTask ().execute(imagePath);
http://developer.android.com/reference/android/os/AsyncTask.html
You can refer to the example on this link.
You don't even use your BitmapWorkerTask! There's no magic that does arbitrary parts of your code in background just when you write some AsyncTaskinto your code. You've got to use it too, you know.
Move the long lasting parts of your codes into the doInBackground() method of the AsyncTask and invoke it like so: new BitmapWorkerTask().execute();
EDIT
To pass the image-path, change the definition of your BitmapWorkerTask like to something like this ... extends AsyncTask<String, Void, Bitmap> ... (note the String instead of the Integer), and pass the image-path as parameter to the execute() method.
new BitmapWorkerTask().execute(imagePath);
Be aware that now this runs asynchronously, so the execute() call returns immediately, but the loading of the image will take some time still.
Also read the Painless Threading article.
Related
I'm trying to upload an image from phone's gallery to a server. I tried Glide and Picasso library. But it is not showing the selected image in ImageView. So, I'm scaling down the image as mentioned here. This method works fine with most of the phones. But I found that it is not working in OnePlus 5 or 5T (Android - Oxygen: 8.0.0). It is showing following exception. The same code works fine if I take a picture using the camera and upload it.
04-14 16:13:43.207 26947-27295/me.example.app W/System.err: java.io.IOException: Resetting to invalid mark
at java.io.BufferedInputStream.reset(BufferedInputStream.java:450)
at me.example.app .Fragment.ImageUploadFragment.decodeSampledBitmapFromUri(ImageUploadFragment.java:172)
at me.example.app .Fragment.ImageUploadFragment$GetResizedBitmapTask.doInBackground(ImageUploadFragment.java:124)
at me.example.app .Fragment.ImageUploadFragment$GetResizedBitmapTask.doInBackground(ImageUploadFragment.java:105)
at android.os.AsyncTask$2.call(AsyncTask.java:333)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
Code for reference:
MainActivity:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_FROM_GALLARY && resultCode == Activity.RESULT_OK){
imageUploadFragment.resizeImage(data.getData(), IMAGE_MAX_DIMENSION);
}
}
....
#Override
public void onBitmapResized(Bitmap resizedBitmap, int maxDimension) {
if (resizedBitmap != null){
Log.i("image", "resized");
imgSelected.setImageBitmap(resizedBitmap);
} else {
Toast.makeText(this, "Something went wrong! Please try again.", Toast.LENGTH_SHORT).show();
}
}
ImageUploadFragment:
public void resizeImage(Uri uri, int maxDimension){
GetResizedBitmapTask getResizedBitmapTask = new GetResizedBitmapTask(maxDimension);
getResizedBitmapTask.execute(uri);
}
#SuppressLint("StaticFieldLeak")
private class GetResizedBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
private int mMaxDimension;
GetResizedBitmapTask(int maxDimension) {
this.mMaxDimension = maxDimension;
}
#Override
protected Bitmap doInBackground(Uri... uris) {
Uri uri = uris[0];
if (uri != null){
Bitmap bitmap;
try {
bitmap = decodeSampledBitmapFromUri(uri, mMaxDimension, mMaxDimension);
return bitmap;
} catch (IOException e) {
e.printStackTrace();
//****this exception I'm getting****
return null;
}
}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (mImageTaskCallbacks != null)
mImageTaskCallbacks.onBitmapResized(bitmap, mMaxDimension);
}
}
public Bitmap decodeSampledBitmapFromUri(Uri imageUri, int reqWidth, int reqHeight) throws IOException{
//gets bytes of data from image file
InputStream inputStream = mContext.getContentResolver().openInputStream(imageUri);
if (inputStream == null){
return null;
}
InputStream bufferedInputStream = new BufferedInputStream(inputStream);
//marks the current position in the InputStream based on the number of bytes remaining to read in the buffer
bufferedInputStream.mark(bufferedInputStream.available());
BitmapFactory.Options options = new BitmapFactory.Options();
//decoder will return null (no bitmap)
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(bufferedInputStream, null, options);
bufferedInputStream.reset();
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
BitmapFactory.decodeStream(bufferedInputStream, null, options);
//****this line of code causing the exception****
bufferedInputStream.reset();
return BitmapFactory.decodeStream(bufferedInputStream, null, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight){
int inSampleSize = 1;
final int width = options.outWidth;
final int height = options.outHeight;
if (width > reqWidth || height > reqHeight){
final int halfWidth = width/2;
final int halfHeight = height/2;
while ((halfWidth/inSampleSize) > reqWidth && (halfHeight/inSampleSize) > reqHeight){
inSampleSize *= 2;
}
}
return inSampleSize;
}
Thank you in advance.
I'm trying to capture an image and set in imageView, like in this code that someone in stackoverflow offered, but i get blank white screen.
what could be the problem?
thanks.
private String pictureImagePath = "";
private void openBackCamera() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = timeStamp + ".jpg";
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
pictureImagePath = storageDir.getAbsolutePath() + "/" + imageFileName;
File file = new File(pictureImagePath);
Uri outputFileUri = Uri.fromFile(file);
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(cameraIntent, 1);
}
Handle Image
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
File imgFile = new File(pictureImagePath);
if(imgFile.exists()){
Bitmap myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
ImageView myImage = (ImageView) findViewById(R.id.imageviewTest);
myImage.setImageBitmap(myBitmap);
}
}
}
Here's the code I used to get bitmap's to display within an ImageView (sorry it's in MonoDroid/C#, it will require light modification to work in Android/Java):
using System.IO;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Widget;
namespace MyApp.Util
{
public static class BitmapHelpers
{
/// <summary>
/// This method will recyle the memory help by a bitmap in an ImageView
/// </summary>
/// <param name="imageView">Image view.</param>
public static void RecycleBitmap(this ImageView imageView)
{
if (imageView == null) {
return;
}
Drawable toRecycle = imageView.Drawable;
if (toRecycle != null) {
((BitmapDrawable)toRecycle).Bitmap.Recycle ();
}
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth)
{
int halfHeight = height / 2;
int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/// <summary>
/// Load the image from the device, and resize it to the specified dimensions.
/// </summary>
/// <returns>The and resize bitmap.</returns>
/// <param name="fileName">File name.</param>
/// <param name="width">Width.</param>
/// <param name="height">Height.</param>
public static Bitmap LoadAndResizeBitmap(this string fileName, int width, int height)
{
// First we get the the dimensions of the file on disk
BitmapFactory.Options options = new BitmapFactory.Options
{
InPurgeable = true,
InJustDecodeBounds = true
};
BitmapFactory.DecodeFile(fileName, options);
options.InSampleSize = calculateInSampleSize(options, width, height);
options.InJustDecodeBounds = false;
Bitmap resizedBitmap = BitmapFactory.DecodeFile(fileName, options);
return resizedBitmap;
}
}
}
Example Usage
public static Bitmap LoadBitmap( File imageFile, int w, int h )
{
Bitmap bitmap = null;
if (imageFile != null)
{
if( (new File(imageFile.Path)).Exists() )
{
bitmap = imageFile.Path.LoadAndResizeBitmap(w, h);
}
}
return bitmap;
}
OnActivityResult Snippet (Makes picture available in Gallery)
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
switch ((ActivityRequestCode)requestCode)
{
case ActivityRequestCode.Camera:
// make it available in the gallery
if (_imageFile != null)
{
Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
Uri contentUri = Uri.FromFile(_imageFile);
mediaScanIntent.SetData(contentUri);
SendBroadcast(mediaScanIntent);
_pictureRequestingFragment.PictureReady(_imageFile);
}
break;
case ActivityRequestCode.Map:
break;
}
}
I am trying to implement AsyncTak to inform the user that a background operation is running when a photo is taken. The AyncTask is able to display the setMessage function but along the line the app crashes and it displays the following error
Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
This is the complete codebase of my AsyncTask
private class CheckTypesTask extends AsyncTask<Void, Void, Void>{
ProgressDialog asyncDialog = new ProgressDialog(MainActivity.this);
#Override
protected void onPreExecute() {
//set message of the dialog
asyncDialog.setMessage("Loading");
//show dialog
asyncDialog.show();
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... arg0) {
//don't touch dialog here it'll break the application
//do some lengthy stuff like calling login webservice
onPhotoTaken();
return null;
}
#Override
protected void onPostExecute(Void result) {
//hide the dialog
asyncDialog.dismiss();
super.onPostExecute(result);
}
}
Then I am calling the AsyncTask in onActivity result this way:
if (resultCode == -1) {
//onPhotoTaken();
(new MainActivity.CheckTypesTask()).execute();
} else {
Log.v(TAG, "User cancelled");
}
}
EDITTED:
This is the onPhotoTaken code and it is defined in MainActivity class.
public void onPhotoTaken() {
_taken = true;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeFile(_path, options);
try {
//ProgressDialog dialog = ProgressDialog.show(this, "Loading", "Please wait...", true);
ExifInterface exif = new ExifInterface(_path);
int exifOrientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
Log.v(TAG, "Orient: " + exifOrientation);
int rotate = 0;
switch (exifOrientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
}
Log.v(TAG, "Rotation: " + rotate);
if (rotate != 0) {
// Getting width & height of the given image.
int w = bitmap.getWidth();
int h = bitmap.getHeight();
// Setting pre rotate
Matrix mtx = new Matrix();
mtx.preRotate(rotate);
// Rotating Bitmap
bitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, false);
}
// Convert to ARGB_8888, required by tess
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
//dialog.dismiss();
} catch (IOException e) {
Log.e(TAG, "Couldn't correct orientation: " + e.toString());
}
Please what could be wrong?
You are trying to access UI components (View) from a background thread in your case inside the doInBackground() method. You are not allowed to do that.
Call your function from onPostExecute()
Try this code
This is your layout which contain an trigger for image capture and an imageview to display captured image :-
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:orientation="vertical"
tools:context="com.serveroverload.cube.ui.HomeActivity" >
<ImageView
android:id="#+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/take_picture"
android:layout_alignParentTop="true"
android:layout_margin="5dp" />
<ImageView
android:id="#+id/take_picture"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="#drawable/flat_selector"
android:padding="5dp"
android:scaleType="fitXY"
android:src="#drawable/take_pic" />
</RelativeLayout>
This is your android manifest with required permisions
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.camtest"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name="com.serveroverload.cube.ui.HomeActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
This is camera handler class to re-size and rotate images if required currently it will sample image to 1024x1280 for UI
public class CamerHandler {
private CamerHandler() {
getAllImages();
}
private static CamerHandler camerHandler;
public static CamerHandler GetCamerHandlerInstance() {
if (null == camerHandler) {
camerHandler = new CamerHandler();
}
return camerHandler;
}
private static final String CAM_DIRECTORY = "CamDirectory";
private static final int MAX_HEIGHT = 1024;
private static final int MAX_WIDTH = 1280;
private ArrayList<File> imageURL = new ArrayList<File>();
public ArrayList<File> getImageURL() {
return imageURL;
}
public void setImageURL(ArrayList<File> imageURL) {
this.imageURL = imageURL;
}
public void getAllImages() {
imageURL.clear();
File folder = new File(getImageDirectory());
File[] listOfFiles = folder.listFiles();
if (null != listOfFiles && listOfFiles.length != 0) {
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
imageURL.add(listOfFiles[i]);
System.out.println("File " + listOfFiles[i].getName());
} else if (listOfFiles[i].isDirectory()) {
System.out.println("Directory " + listOfFiles[i].getName());
}
}
}
}
/**
* This method is responsible for solving the rotation issue if exist. Also
* scale the images to 1024x1024 resolution
*
* #param context
* The current context
* #param selectedImage
* The Image URI
* #return Bitmap image results
* #throws IOException
*/
public Bitmap handleSamplingAndRotationBitmap(Context context, Uri selectedImage) throws IOException {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
BitmapFactory.decodeStream(imageStream, null, options);
imageStream.close();
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
imageStream = context.getContentResolver().openInputStream(selectedImage);
Bitmap img = BitmapFactory.decodeStream(imageStream, null, options);
// img = rotateImageIfRequired(img, selectedImage);
img = rotateBitmap(context, img, selectedImage);
return img;
}
public Bitmap rotateBitmap(Context context, Bitmap bitmap, Uri selectedImage) {
ExifInterface exif;
try {
exif = new ExifInterface(selectedImage.getPath());
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
Matrix matrix = new Matrix();
switch (orientation) {
case ExifInterface.ORIENTATION_NORMAL:
return bitmap;
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
matrix.setScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.setRotate(180);
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
matrix.setRotate(180);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
matrix.setRotate(90);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.setRotate(90);
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
matrix.setRotate(-90);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.setRotate(-90);
break;
default:
return bitmap;
}
// try {
Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
return bmRotated;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (OutOfMemoryError e) {
e.printStackTrace();
return null;
}
}
/**
* Calculate an inSampleSize for use in a {#link BitmapFactory.Options}
* object when decoding bitmaps using the decode* methods from
* {#link BitmapFactory}. This implementation calculates the closest
* inSampleSize that will result in the final decoded bitmap having a width
* and height equal to or larger than the requested width and height. This
* implementation does not ensure a power of 2 is returned for inSampleSize
* which can be faster when decoding but results in a larger bitmap which
* isn't as useful for caching purposes.
*
* #param options
* An options object with out* params already populated (run
* through a decode* method with inJustDecodeBounds==true
* #param reqWidth
* The requested width of the resulting bitmap
* #param reqHeight
* The requested height of the resulting bitmap
* #return The value to be used for inSampleSize
*/
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and
// width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee a final image
// with both dimensions larger than or equal to the requested height
// and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger
// inSampleSize).
final float totalPixels = width * height;
// Anything more than 2x the requested pixels we'll sample down
// further
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}
public void openGallery(Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("content://media/internal/images/media"));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
private void convertToBase64(Bitmap bitmap) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.JPEG, 30, byteArrayOutputStream);
byte[] byteArray = byteArrayOutputStream.toByteArray();
String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP);
// bitmap.recycle();
encoded = null;
byteArray = null;
}
public String getImageDirectory() {
return createDirIfNotExists().getAbsolutePath();
}
public File createDirIfNotExists() {
File imageDirectory = new File(Environment.getExternalStorageDirectory(), CAM_DIRECTORY);
if (!imageDirectory.exists()) {
if (!imageDirectory.mkdirs()) {
Log.e("imageDirectory:: ", "Problem creating Image folder");
}
}
return imageDirectory;
}
}
and this is activity code which take pictures and perform all down sampling and image rotation in background before updating on UI
public class HomeActivity extends Activity {
static final int REQUEST_IMAGE_CAPTURE = 1;
private ImageView previewLayout;
private static final String TAG = "MainActivity";
static Uri capturedImageUri = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// Imageview to display Image
previewLayout = (ImageView) findViewById(R.id.preview);
findViewById(R.id.take_picture).setOnClickListener(
new OnClickListener() {
#Override
public void onClick(View v) {
dispatchTakePictureIntent();
}
});
}
private void dispatchTakePictureIntent() {
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
if (intent.resolveActivity(getPackageManager()) != null) {
Calendar cal = Calendar.getInstance();
// store image in new File in image directory
File file = new File(CamerHandler.GetCamerHandlerInstance()
.getImageDirectory(), (cal.getTimeInMillis() + ".png"));
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Toast.makeText(getApplicationContext(),
"Failed to make file", 500).show();
}
} else {
file.delete();
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Toast.makeText(getApplicationContext(),
"Failed to make file", 500).show();
}
}
capturedImageUri = Uri.fromFile(file);
intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri);
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
}
}
#SuppressLint("NewApi")
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == REQUEST_IMAGE_CAPTURE) {
// update file in gallery
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
capturedImageUri));
// Downsample image before displaying in imageview to avoi OOM
// exception
new LoadBitMap(previewLayout, HomeActivity.this)
.execute(capturedImageUri);
} else {
Log.e(TAG, "FAILED TO TAKE IMAGE");
}
}
}
class LoadBitMap extends AsyncTask<Uri, Void, Void> {
public LoadBitMap(ImageView preview, Context context) {
this.prevImageView = preview;
this.mContext = context;
}
private Bitmap bitmap = null;
private ImageView prevImageView;
private Context mContext;
#Override
protected Void doInBackground(Uri... params) {
try {
bitmap = CamerHandler.GetCamerHandlerInstance()
.handleSamplingAndRotationBitmap(mContext, params[0]);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
if (null != bitmap) {
prevImageView.setBackground(new BitmapDrawable(mContext
.getResources(), bitmap));
}
super.onPostExecute(result);
}
}
Points to note here you can use Picasso or Glide for image loading task as we already have uri path in captureuri variable.
I was having a problem where my bitmaps were causing my application to skip frames at a rapid rate. I have looked into android's "Displaying Bitmap's Efficiently" documentation, and have implemented some into my code.
I am pretty sure the image is loading correctly, but its not showing in my application. The height and width of the image view is 0 after I do this decoding, so this might be one of the reasons, but I can't change the size of the ImageView for some reason.
BitmapWorkerTask.java:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
private Context context;
public BitmapWorkerTask(ImageView imageView, Context context) {
this.context = context;
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
System.out.println("T" + buildBitmap(data, 100, 100));
return buildBitmap(data, 100, 100);
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask =
AsyncDrawable.getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
public Bitmap buildBitmap(int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(context.getResources(), resId, options);
}
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int width = options.outWidth;
final int height = options.outHeight;
int inSampleSize = 1;
if(height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData == 0 || bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
}
}
I am creating the image in the constructor of my View class, and then drawing the TextView in the onDraw method.
public Game(Context context) {
loadBitmap(R.drawable.logo, scoreHolderView);
}
#Override
protected void onDraw(Canvas canvas) {
if(STATE == State.Playing) {
synchronized(spawner.activeTiles) {
for(Iterator<Tile> iter = spawner.activeTiles.iterator(); iter.hasNext();) {
Tile tile = iter.next();
canvas.drawRect(tile, tile.getColor());
}
}
//canvas.drawBitmap(scoreholder, null, scoreIcon, null);
canvas.drawText(String.valueOf(SCORE), 50, 50, blackText);
scoreHolderView.draw(canvas);
}
}
Is the position and size of the ImageView just not being set for some reason? Or are there other problems with my code?
EDIT:
public void loadBitmap(int resId, ImageView imageView) {
if (AsyncDrawable.cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView, getContext());
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), logo, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
For my application, I have a texture in high resolution. To reduce the size for small screens I do like that:
#Override
public void onLoadResources(){
Options options = new BitmapFactory.Options();
options.inScaled = false;
// calculation inSampleSize
int sm = 1;
if (cameraWidth+cameraHeight < 1280) sm = 2;// < 800x480
if (cameraWidth+cameraHeight < 800) sm = 4;// < 480x320
options.inSampleSize = sm;
mTexture = new BitmapTextureAtlas(2048/sm, 2048/sm, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
// Loading bitmap
sky_bm = BitmapFactory.decodeResource(getResources(), R.drawable.sky, options);
sky_src = new BitmapTextureAtlasSource(sky_bm);
skyRegion = TextureRegionFactory.createFromSource(mTexture, sky_src, 0, 0, false);
mEngine.getTextureManager().loadTexture(mTexture);
BitmapTextureAtlasSource code:
public class BitmapTextureAtlasSource extends BaseTextureAtlasSource implements IBitmapTextureAtlasSource {
private Bitmap mBitmap;
public BitmapTextureAtlasSource(Bitmap pBitmap) {
super(0,0);
//this.mBitmap = pBitmap;
this.mBitmap = pBitmap.copy(Bitmap.Config.ARGB_8888, false);
}
public int getWidth() {
return mBitmap.getWidth();
}
public int getHeight() {
return mBitmap.getHeight();
}
#Override
public BitmapTextureAtlasSource clone() {
return new BitmapTextureAtlasSource(Bitmap.createBitmap(mBitmap));
}
public Bitmap onLoadBitmap(Config pBitmapConfig) {
return mBitmap;
}
#Override
public IBitmapTextureAtlasSource deepCopy() {
return null;
}
}
But when rotating the screen, I get the error:
FATAL EXCEPTION: GLThread 4895
java.lang.IllegalArgumentException: bitmap is recycled
at android.opengl.GLUtils.texSubImage2D(GLUtils.java:220)
at org.anddev.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas.writeTextureToHardware(BitmapTextureAtlas.java:162)
at org.anddev.andengine.opengl.texture.Texture.loadToHardware(Texture.java:116)
at org.anddev.andengine.opengl.texture.TextureManager.updateTextures(TextureManager.java:146)
at org.anddev.andengine.engine.Engine.onDrawFrame(Engine.java:507)
at org.anddev.andengine.opengl.view.RenderSurfaceView$Renderer.onDrawFrame(RenderSurfaceView.java:154)
at net.rbgrn.opengl.GLThread.guardedRun(GLThread.java:235)
at net.rbgrn.opengl.GLThread.run(GLThread.java:94)
Please tell me what I'm doing wrong. I would be grateful for any information
I think the problem could be on onLoadBitmap, you should return a copy. I suggest you to try this implementation extending EmptyBitmapTextureAtlasSource:
public class BitmapTextureSource extends EmptyBitmapTextureAtlasSource {
private Bitmap mBitmap;
public BitmapTextureSource(Bitmap bitmap) {
super(bitmap.getWidth(), bitmap.getHeight());
mBitmap = bitmap;
}
#Override
public Bitmap onLoadBitmap(Config pBitmapConfig) {
return mBitmap.copy(pBitmapConfig, true);
}
}
I suggest you set your resolution policy to RatioResolutionPolicy and let the engine do the scaling for you.
http://andengine.wikidot.com/detect-screen-resolution