I'm making a project Android application that takes an image URL, downloads the image and displays the image. In case of an image of bigger size i want to show the user an indeterminate progress that the image is being downloading.
Java Code:
public class MainActivity extends AppCompatActivity {
ImageView downloadedImg;
ProgressBar progressBar;
Handler handler;
public void downloadImage(View view){
progressBar.setVisibility(View.VISIBLE);
ImageDownloader task = new ImageDownloader();
Bitmap myimage;
try {
myimage = task.execute("http://wallpaperswide.com/download/high_tech_earth-wallpaper-2880x1800.jpg").get();
downloadedImg.setImageBitmap(myimage);
}
catch (Exception e) {
e.printStackTrace();
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
downloadedImg = (ImageView)findViewById(R.id.imageView);
progressBar = (ProgressBar)findViewById(R.id.pbar);
handler = new Handler();
}
public class ImageDownloader extends AsyncTask<String,Void,Bitmap>{
protected void onPreExecute(){
super.onPreExecute();
//progressBar.setVisibility(View.VISIBLE);
}
#Override
protected Bitmap doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.connect();
InputStream inputStream = connection.getInputStream();
Bitmap mybitmap = BitmapFactory.decodeStream(inputStream);
return mybitmap;
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
handler.post(new Runnable() {
#Override
public void run() {
progressBar.setVisibility(View.GONE);
downloadedImg.setVisibility(View.VISIBLE);
}
});
}
}
public void reset(View view){
downloadedImg.setVisibility(View.GONE);
progressBar.setVisibility(View.INVISIBLE);
}
}
XML code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="match_parent"
tools:context="com.example.syeddanish.downloadingimages.MainActivity">
<Button
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:onClick="downloadImage"
android:text="Download Image" />
<Button
android:id="#+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/button"
android:onClick="reset"
android:text="Reset" />
<ProgressBar
android:id="#+id/pbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:indeterminate="true"
android:visibility="invisible" />
<ImageView
android:id="#+id/imageView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:visibility="gone"
android:layout_below="#+id/button1" />
</RelativeLayout>
The issue i'm facing is that i want the progress bar to become visible when the download starts(i.e when "Download Image button is pressed"). I'm trying to do this in two ways i.e
By using progressbar.setVisibility(View.VISIBLE); in the start of
the onClick method of "Download Image" button.
or
By using progressbar.setVisibility(View.VISIBLE); in theonPreExecute() method of the ASyncTask but the progress bardoes not shows up using any of the above mentioned ways.
Can anyone please point out what i am doing wrong?
Does this code compile and run without NetworkOnMainThreadException?
Your problem is the usage of get()
In this part:
Bitmap myimage;
try {
myimage = task.execute("http://wallpaperswide.com/download/high_tech_earth-wallpaper-2880x1800.jpg").get();
downloadedImg.setImageBitmap(myimage);
}
you try to get an image from task.execute(...), but task.get() as per docu:
[...]Waits if necessary for the computation to complete, and then retrieves its result.[...]
So you are waiting for your "task" to execute on the main thread and blocking it, until done. Because of that, your progress
is never visible, because the UI-Thread is blocked. And once your task finishes,
the progress is set back to be invisible.
Moreover, do not reinvent the weel. Use one of the libraries available out there
for image downloading and caching.
For example: Picasso, Glide
Both also provide the functionality to use a (1) fallback and (2) loading image.
If you still like to try it on your own, then do not do the Pokémon- "gotta catch'em all" way of catching your exceptions, but instead, handle specific Exceptions that might occur and display a message to the user, send it your crash tracker, etc. Only catch exceptions that you expect to be thrown, otherwise...let it crash.
I do not see, why you should catch an exception there.
AsyncTask, Activities and memory leaks
Next is, that AsyncTasks are not tidily coupled to the Activities
life cycle. When you run your task and it executes in background, but
your activity finishes, this task will still be alive and leaks a reference to your activity. This causes the memory leaks, because the GC can't properly do it's job, to clean after you.
Make your AsyncTask at least a static class and stop/kill the task, once your activity finishes.
Multiple Tasks
Next thing, check if you already download the image, once the user clicked the button, or you're going to create multiple tasks.
So, make your ImageDownloader a member of your activity and check if it is already executing or done. (Take it out of your method and put it below the activity class head). When your activity calls onPause() or onDestroy(), kill the task with fire.
Be aware of orientation changes, too.
Android Task API instead of AsyncTask
I highly recommend to use the android task api. (com.google.android.gms.tasks)
It works very well for tasks, both running on the Main- or Workerthreads. Include continuations, provides Future like functionality and can be coupled with Activities.
References: gms Task API Doc
try put downloadedImg.setImageBitmap(myimage); inside onPostExecute(Bitmap bitmap)handler and change:
myimage = task.execute("http://wallpaperswide.com/download/high_tech_earth-wallpaper-2880x1800.jpg").get();
to:
task.execute("http://wallpaperswide.com/download/high_tech_earth-wallpaper-2880x1800.jpg");
also put Bitmap myimage; as global variable on your Asynctask class and change:
Bitmap mybitmap = BitmapFactory.decodeStream(inputStream);
return mybitmap;
to:
myimage= BitmapFactory.decodeStream(inputStream);
on doInBackground
Consider using pBar.setAlpha(1f);
It helped when I'm stuck on a similar problem.
Related
I want to use Glide to eagerly download images and cache them on disk for a future use. I want to call this functionality from a background thread.
I've read Glide's caching documentation, but it doesn't explain how to download the image without having an actual target right now. Then I found this issue and tried to use a similar approach, but whatever I try I get this exception:
java.lang.IllegalArgumentException: You must call this method on the main thread
So, how can I tell Glide to cache an image from a background thread?
EDIT: I really want to call Glide's methods on background thread. I know that I can use Handler and other ways to offload it to UI thread, but that's not what I'm asking.
GlideApp.with(context)
.downloadOnly()
.diskCacheStrategy(DiskCacheStrategy.DATA) // Cache resource before it's decoded
.load(url)
.submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.get() // Called on background thread
If want to load images in cache for future use in background thread then Glide have this functionality
here how you can do this
//here i passing application context so our glide tie itself with application lifecycle
FutureTarget<File> future = Glide.with(getApplicationContext()).downloadOnly().load("your_image_url").submit();
now if you want to retrieve the saved path that you want to store in your DB then you can do
File file = future.get();
String path = file.getAbsolutePath();
you can also do this in just one line returning a path string like this
String path = Glide.with(getApplicationContext()).downloadOnly().load("your_image_url").submit().get().getAbsolutePath();
You must call this method on the main thread
Wherever you call a method to use Glide or doing something from background, run inside:
runOnUiThread(new Runnable() {
#Override
public void run() {
// Here, use glide or do your things on UiThread
}
});
To use it inside the main thread and then error should be gone.
Did I get the question right?
private class update extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... params) {
try {
RequestOptions options = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL) // Saves all image resolution
.centerCrop()
.priority(Priority.HIGH)
.placeholder(R.drawable.null_image_profile)
.error(R.drawable.null_image_profile);
Glide.with(context).load(imageUrl)
.apply(options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
//finish
}
}
I need to process some data when the user click the button in one activity, so the screen looks like the app stops for 2-3 seconds. It isn't a lot but I want to give the user information that everything is ok and IMO the best way will be the progressbar which is visible only when data are processed.
I found the code of ProgressBar and it looks like this:
<ProgressBar
android:id="#+id/loadingdata_progress"
style="?android:attr/progressBarStyle"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignBottom="#+id/fin2_note"
android:layout_centerHorizontal="true"
android:indeterminate="true"
android:visibility="invisible" />
and inserted it on the middle of my layout.
And to try if the progressbar works, I put this code
loadingimage= (ProgressBar) findViewById(R.id.loadingdata_progress);
loadingimage.setVisibility(View.VISIBLE);
into onCreate method and everything looks fine.
Then I recreated the code to show this progressbar only if the data is processed.
After click the user invoke this method
public void fin2_clickOnFinalization(View v)
{
loadingimage= (ProgressBar) findViewById(R.id.loadingdata_progress);
loadingimage.setVisibility(View.VISIBLE);
// code where data is processing
loadingimage.setVisibility(View.INVISIBLE);
}
and nothing appear on the screen. I don't know where is the mistake. If I found the progress bar by id, It's strange for me that I can control it in onCreate method but in onclick method it's out of my control.
Your UI thread cannot show progress bar cause it is busy due to your data processing. Try to use this kind of code :
public void fin2_clickOnFinalization(View v) {
new YourAsyncTask().execute();
}
private class YourAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... args) {
// code where data is processing
return null;
}
#Override
protected void onPostExecute(Void result) {
loadingimage.setVisibility(View.INVISIBLE);
super.onPostExecute(result);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
loadingimage.setVisibility(View.VISIBLE);
}
}
EDIT:
AsyncTask let you run code in separate thread and make app more responsive, just put time-consuming code inside doInBackground.
You're not giving the UI time to refresh. Your "data processing" code is running on the UI thread, blocking any visible changes. By the time the system gets control to refresh the display, you've already set it back to invisible.
To fix this, move your processing code to a separate thread or AsyncTask. Then you can set the progress bar to visible, start the task, and have it turn itself invisible once it's done.
I'd recommend AsyncTask for this purpose about 90% of the time on Android, since it comes stock with useful callbacks. The developer guide for it(in the Javadoc linked above) is pretty explicit, and outlines all the steps you need to take.
AsyncTask is too heavily-weighted for such task.
A better much solution
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
loadingimage.setVisibility(View.VISIBLE);
}
});
Or even simpler (does essentially the same thing as solution above)
runOnUiThread(new Runnable() {
#Override
public void run() {
loadingimage.setVisibility(View.VISIBLE);
}
});
You can try to create a global ProgressDialog not in the layout but in your activity like:
public class MyActivity {
ProgressDialog progress = null;
protected void onCreate(...) {
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setTitle("Progress");
}
public void fin2_clickOnFinalization(View v)
{
progress.show();
// code where data is processing
progress.dismiss();
}
}
Hope i it helps
I would like to load a large/complex svg image file into my imageView. Loading the svg takes time to load on my HTC Desire S and Lenovo a60. About 5 Second and 10 seconds respectively.
Right now my app becomes unresponsive for about 5 seconds until the imageView is fully loaded
I load the image using this simple code..
svg = SVGParser.getSVGFromResource(getResources(), R.raw.gf);
mImageView.setImageDrawable(svg.createPictureDrawable());
I was looking something like (webView)
webView.setWebViewClient(new WebViewClientEx(MainActivity.this, DEBUG) {
#Override
public void onPageFinished(final WebView view, String url) {
...........
.............}
});
which I used in my previous project...
Finally my questions:
1.) What is the best approach to make the app look responsive?
I was planning to use asynctask but don't know how to use it... Is it applicable here?
2.) Is there listener after the image is fully loaded?
My approach here is to show the progressDialog by the time I load the imageView and hide it after the imageView has fully loaded.
another other suggestions which you think is better to use for showing/hiding progressDialog ? Thanks!
Use an async task here. (a Loader would work aswell but async task is a bit easier to explain.)
private class LoadSVGTask extends AsyncTask<Integer, Void, Drawable> {
protected Drawable doInBackground(Integer... res) {
mProgressBar.setVisibility(View.Visible);
svg = SVGParser.getSVGFromResource(getResources(),res);
Drawable d = svg.createPictureDrawable();
return d;
}
// gets executed in main thread
protected void onPostExecute(Drawable result) {
mProgressBar.setVisibility(View.Gone);
mImageView.setImageDrawable(result);
}
}
Launch the Task with:
new LoaderSVGTask().execute(R.raw.gf, null, null);
see: http://developer.android.com/reference/android/os/AsyncTask.html
Just refer AsyncTask in that
protected void onPreExecute (){
progressDialog.show(......);
}
protected abstract Result doInBackground (Params... params){
//load your image from here
}
protected void onPostExecute (Result result){
progressDialog.dismiss();
}
This question has probably been asked a lot many times.. Yes! I am facing the so called "black screen problem"..
My first activity is which the main activity, in which login is done, and this activity is followed by the next one which is a simple summary report of the user's account.
Now, for this report, i fetch the dataset, parse it and use it to build the table dynamically.
The villan black screen appears here!! The report, as in the table is rendered properly, but upon that screen, this black tranluscent layer keeps appearing.
I tried everything, using handlers, asynctask, translucent theme but nothing helps!! The villan still appears when my summary report loads. It goes away if i press the "Back" button, and the screen appears normal, as it is expected to be when it loads the first time.. I cant figure out what exactly is going wrong, whether, its my coding approach(dynamically generating the table) or it is an emulator problem or what.
My emulator details are as follows:
CPU:ARM
Target: Android 2.3.3
skin: WVGA800
sd card:1024M
Someone please rescue me!!
EDIT:
In my Second activity i do the following:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_summary_report);
ad = new AlertDialog.Builder(this).create();
boolean is = IsConnectedToNetwork();
if (is == true){
Handler execWS = new Handler();
execWS.post(callWS);//Fetch Data via web service and parse it
if(result != null){
Handler genUI = new Handler();
genUI.post(createUI);// generate the table based on "result". ie. in a for loop i create textviews and append it to the table layout
}
}
else{
Error = "Connection Failed. Try Again!";
}
if(Error.length() != 0){
ad.setMessage(Error);
ad.setTitle("Error..");
ad.show();
}
}
My Xml layout for the second activity..
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:baselineAligned="false"
android:gravity="center"
android:orientation="vertical">
<TableLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dip">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:contentDescription="#string/Logo"
android:src="#drawable/complogo"
android:gravity="top"/>
<TextView
android:id="#+id/lblLoginInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"/>
</TableLayout>
<TableLayout
android:id="#+id/tblSummRep"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="4"
android:padding="5dip">
</TableLayout>
<TableLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="5dip">
<TextView
android:id="#+id/AppName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#string/AppName"
android:textColor="#ff6600" />
<TextView
android:id="#+id/AppVersion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#string/AppVersion"
android:textColor="#ff6600" />
<TextView
android:id="#+id/Disclaimer"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/Disclaimer"
android:textColor="#ff6600" />
</TableLayout>
</LinearLayout>
I append the textview to "tblSummRep"..
UPDATE
my asynctask class..
private class ShareWork extends AsyncTask<Void, Void, Boolean>{
ProgressDialog pDlg;
//String[][] result = null;
protected void onPreExecute(){
pDlg.setMessage("Please wait while the Report Loads...");
pDlg.show();
}
#Override
protected Boolean doInBackground(Void... params) {
boolean RetVal = false;
//my fetch code
return RetVal;
}
protected void onPostExecute(Boolean value){
if(value == true){
pDlg.setMessage("Please try again later!!");
pDlg.show();
}
else{
createUI.run();
}
}
}
I do not know what do you refer to by "black screen problem", but you state that you are fetching data (I guess from a remote server) and that happens. That sounds to me about blocking the UI thread. In which method of the activity are you fetching the data (maybe in the onCreate)? You should not perform time-consuming actions on the graphical thread, such as querying a remote server (which could take some seconds). In these cases always an AsyncTask should be used. The user should receive some feedback about what is going on in the background, so using a ProgressBar is normally recommendable (or at least a spinner).
You can take a look about how to solve a similar problem with an AsyncTask here.
Update
You have just posted your code. The problem lies (as I presumed) in this line of code:
execWS.post(callWS);//Fetch Data via web service and parse it
I guess this is a WS call. You are blocking here the UI thread. Create an AsyncTask the same way as the one of the link I provided, and upon completion of the task execute the rest of the code (display error or the dynamic table with the data).
Your AsyncTask could look like this (I have not tried it):
private class LoadTableTask extends AsyncTask<URL, Integer, Object> {
protected Object doInBackground(URL... urls) {
Handler execWS = new Handler();
execWS.post(urls[0]);//Fetch Data via web service and parse it
return result;
}
protected void onProgressUpdate(Integer... progress) {
//Not used in your case. It would be a good idea to create an undefined progress dialog in your case
}
protected void onPostExecute(Object result) {
if(result != null){
Handler genUI = new Handler();
genUI.post(createUI);// generate the table based on "result". ie. in a for loop i create textviews and append it to the table layout
}
}
}
Your onCreate() method would be replaced by:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_summary_report);
ad = new AlertDialog.Builder(this).create();
boolean is = IsConnectedToNetwork();
if (is == true){
new LoadTableTask().execute(callWS);
}
else{
Error = "Connection Failed. Try Again!";
}
if(Error.length() != 0){
ad.setMessage(Error);
ad.setTitle("Error..");
ad.show();
}
}
I want when i press a specific Button an Image (like a map) to be displayed. What is more light-weight for my application? Getting this from a url or having it on the drawable folder and just display it?
If I choose the second one, and want to implement a "back" button I will have to put the whole thing in an extra class or not?
My application needs connection to the Internet, regardless of this.
I'd go with a drawable, purely for this reason, what happens when the user starts the app with no wifi/3g/etc. or an extremely slow connection. You say your application needs a connection, but that doesn't necessarily mean the user will have it enabled when they start the app.
Its also a lot easier with a drawable, simply put it into the drawable folder then specify it as source for your ImageView (if you are using a clickable imageview)
<ImageView android:layout_height="wrap_content" android:id="#+id/imageView1"
android:layout_width="wrap_content" android:src="#drawable/your_image">
</ImageView>
or as the background if you are using a Button in the xml file.
<Button android:text="" android:id="#+id/button1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:background="#drawable/your_image">
</Button
though you may want to use a selector to change between 2 images (pressed and unpressed state)
and instead specify the selector xml file as the background/source
ie android:background="#drawable/back_button_selector"
Downloading an image needs to be done in a background thread (such as an AsycnTask) or the UI will not respond while the image is downloading.
But if you decide to download the image for some reason (ie. you want to have the image change without putting out an update and just changing it on the server) here's an AsyncTask to download an image (you can use it as an inner class)
public class GetImage extends AsyncTask<ImageView, Void, ImageView> {
String url = null;
Bitmap thumbnail = null;
public GetImage(String url){
this.url = url;
}
#Override
protected void onPreExecute() {
}
#Override
protected ImageView doInBackground(ImageView... params) {
try {
thumbnail = BitmapFactory.decodeStream((InputStream) new URL(url).getContent());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return params[0];
}
#Override
public void onPostExecute(ImageView result) {
result.setImageBitmap(thumbnail);
}
}