layout fix, added asynctask for saving images, memory leak fix

This commit is contained in:
nuclearfog 2021-02-24 00:45:26 +01:00
parent 8daf1fa1a5
commit 3b736b2d3c
No known key found for this signature in database
GPG Key ID: D5490E4A81F97B14
4 changed files with 143 additions and 72 deletions

View File

@ -23,8 +23,10 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.ImageSaver;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
@ -34,6 +36,7 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.content.Intent.ACTION_PICK;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.AsyncTask.Status.RUNNING;
import static android.os.Environment.DIRECTORY_PICTURES;
import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
import static android.widget.Toast.LENGTH_SHORT;
@ -43,7 +46,7 @@ import static android.widget.Toast.LENGTH_SHORT;
*
* @author nuclearfog
*/
abstract class MediaActivity extends AppCompatActivity implements LocationListener, Runnable {
public abstract class MediaActivity extends AppCompatActivity implements LocationListener {
/**
* permission set
@ -96,14 +99,8 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
*/
protected static final int REQUEST_STORE_IMG = 9;
/**
* Quality of the saved jpeg images
*/
private static final int JPEG_QUALITY = 90;
@Nullable
private LocationManager locationManager;
private Thread imageSaveThread;
private ImageSaver imageTask;
private Bitmap image;
private String filename;
private boolean locationPending = false;
@ -112,14 +109,20 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
@Override
protected void onCreate(Bundle b) {
super.onCreate(b);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
}
@Override
protected void onDestroy() {
if (locationManager != null)
locationManager.removeUpdates(this);
if (locationPending) {
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null) {
locationManager.removeUpdates(this);
}
}
if (imageTask != null && imageTask.getStatus() == RUNNING) {
imageTask.cancel(true);
}
super.onDestroy();
}
@ -136,8 +139,9 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
else
onAttachLocation(null);
} else if ((PERMISSIONS[2][0].equals(permissions[0]))) {
if (grantResults[0] == PERMISSION_GRANTED)
writeImageToStorage();
if (grantResults[0] == PERMISSION_GRANTED) {
saveImage();
}
}
}
}
@ -161,54 +165,36 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
}
}
@Override
public final void run() {
boolean imageSaved = false;
/**
* save image to external storage
*/
private void saveImage() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// use scoped storage
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.MediaColumns.RELATIVE_PATH, DIRECTORY_PICTURES);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri imageUri = getContentResolver().insert(EXTERNAL_CONTENT_URI, values);
if (imageUri != null) {
OutputStream fileStream = getContentResolver().openOutputStream(imageUri);
if (fileStream != null) {
imageSaved = image.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, fileStream);
fileStream.close();
if (imageTask == null || imageTask.getStatus() != RUNNING) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// store images directly
File imageFile = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES), filename);
OutputStream fileStream = new FileOutputStream(imageFile);
imageTask = new ImageSaver(this);
imageTask.execute(image, fileStream);
} else {
// use scoped storage
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.MediaColumns.RELATIVE_PATH, DIRECTORY_PICTURES);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri imageUri = getContentResolver().insert(EXTERNAL_CONTENT_URI, values);
if (imageUri != null) {
OutputStream fileStream = getContentResolver().openOutputStream(imageUri);
imageTask = new ImageSaver(this);
imageTask.execute(image, fileStream);
}
}
} else {
// store images directly
File imageFile = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES), filename + ".jpg");
OutputStream fileStream = new FileOutputStream(imageFile);
imageSaved = image.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, fileStream);
fileStream.close();
// start media scanner to scan for new image
String[] fileName = {imageFile.toString()};
MediaScannerConnection.scanFile(getApplicationContext(), fileName, null, null);
}
} catch (Exception err) {
} catch (FileNotFoundException err) {
err.printStackTrace();
}
if (imageSaved) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), R.string.info_image_saved, Toast.LENGTH_LONG).show();
}
});
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), R.string.error_image_save, Toast.LENGTH_SHORT).show();
}
});
}
}
@ -235,6 +221,25 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
public final void onStatusChanged(String provider, int status, Bundle extras) {
}
/**
* called when an image was successfully saved to external storage
*/
public void onImageSaved() {
Toast.makeText(getApplicationContext(), R.string.info_image_saved, Toast.LENGTH_LONG).show();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// start media scanner to scan for new image
String[] fileName = {filename};
MediaScannerConnection.scanFile(getApplicationContext(), fileName, null, null);
}
}
/**
* called when an error occurs while storing image
*/
public void onError() {
Toast.makeText(getApplicationContext(), R.string.error_image_save, Toast.LENGTH_SHORT).show();
}
/**
* Ask for GPS location
*/
@ -268,9 +273,12 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
protected void storeImage(Bitmap image, String filename) {
this.image = image;
this.filename = filename;
if (!filename.endsWith(".jpg")) {
this.filename += ".jpg";
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& checkSelfPermission(PERMISSIONS[2][0]) == PERMISSION_GRANTED) {
writeImageToStorage();
|| checkSelfPermission(PERMISSIONS[2][0]) == PERMISSION_GRANTED) {
saveImage();
} else {
requestPermissions(PERMISSIONS[2], REQUEST_STORE_IMG);
}
@ -290,6 +298,7 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
*/
@SuppressLint("MissingPermission") // suppressing because of an android studio bug
private void fetchLocation() {
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, null);
locationPending = true;
@ -331,16 +340,6 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
}
}
/**
* start background thread to save image
*/
private void writeImageToStorage() {
if (imageSaveThread == null || !imageSaveThread.isAlive()) {
imageSaveThread = new Thread(this);
imageSaveThread.start();
}
}
/**
* called when location information was successfully fetched
*

View File

@ -40,6 +40,7 @@ import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.zoomview.ZoomView;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@ -116,6 +117,7 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
IDLE
}
private WeakReference<MediaViewer> updateEvent = new WeakReference<>(this);
@Nullable
private ScheduledExecutorService progressUpdate;
@Nullable
@ -186,14 +188,19 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
controlPanel.setVisibility(VISIBLE);
if (!mediaLinks[0].startsWith("http"))
share.setVisibility(GONE); // local image
final Runnable seekUpdate = new Runnable() {
public void run() {
if (updateEvent.get() != null) {
updateEvent.get().updateSeekBar();
}
}
};
progressUpdate = Executors.newScheduledThreadPool(1);
progressUpdate.scheduleWithFixedDelay(new Runnable() {
public void run() {
runOnUiThread(new Runnable() {
public void run() {
updateSeekBar();
}
});
if (updateEvent.get() != null) {
updateEvent.get().runOnUiThread(seekUpdate);
}
}
}, PROGRESS_UPDATE, PROGRESS_UPDATE, TimeUnit.MILLISECONDS);
case MEDIAVIEWER_ANGIF:

View File

@ -4,6 +4,7 @@ import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import org.nuclearfog.twidda.R;
@ -23,9 +24,11 @@ public final class ImageItem extends RecyclerView.ViewHolder {
public ImageItem(View view, GlobalSettings settings) {
super(view);
CardView cardBackground = (CardView) view;
preview = view.findViewById(R.id.item_image_preview);
saveButton = view.findViewById(R.id.item_image_save);
saveButton.setImageResource(R.drawable.save);
cardBackground.setCardBackgroundColor(settings.getCardColor());
AppStyles.setButtonColor(saveButton, settings.getFontColor());
AppStyles.setDrawableColor(saveButton, settings.getIconColor());
}

View File

@ -0,0 +1,62 @@
package org.nuclearfog.twidda.backend;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import org.nuclearfog.twidda.activity.MediaActivity;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
/**
* background task to save an image to the external storage
*
* @author nuclearfog
* @see MediaActivity
*/
public class ImageSaver extends AsyncTask<Object, Void, Boolean> {
/**
* Quality of the saved jpeg images
*/
private static final int JPEG_QUALITY = 90;
private WeakReference<MediaActivity> callback;
public ImageSaver(MediaActivity activity) {
super();
callback = new WeakReference<>(activity);
}
@Override
protected Boolean doInBackground(Object... data) {
try {
if (data != null && data.length == 2) {
if (data[0] instanceof Bitmap && data[1] instanceof OutputStream) {
Bitmap image = (Bitmap) data[0];
OutputStream fileStream = (OutputStream) data[1];
boolean imageSaved = image.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, fileStream);
fileStream.close();
return imageSaved;
}
}
} catch (Exception err) {
err.printStackTrace();
}
return false;
}
@Override
protected void onPostExecute(Boolean success) {
if (callback.get() != null) {
if (success) {
callback.get().onImageSaved();
} else {
callback.get().onError();
}
}
}
}