I am integrating giphy to my android app..
How can i play animated gif image from URL in android? Should I use ImageView, WebView, VideoView etc? For example if i want to play animation from this URL.
Just create a html string and load that into android webview. Tested solution.
http://developer.android.com/reference/android/webkit/WebView.html#loadData(java.lang.String, java.lang.String, java.lang.String)
String x = "<!DOCTYPE html><html><body><img src=\"http://goo.gl/uPJ9P2\" alt=\"Smileyface\" width=\"100%\" height=\"100%\"></body></html>";
webView.loadData(x, "text/html", "utf-8");
Try this way
Movie movie;
GifView(Context context) {
super(context);
movie = Movie.decodeStream(
context.getResources().openRawResource(
R.drawable.some_gif));
}
#Override
protected void onDraw(Canvas canvas) {
if (movie != null) {
movie.setTime(
(int) SystemClock.uptimeMillis() % movie.duration());
movie.draw(canvas, 0, 0);
invalidate();
}
}
I'd recommend using a third-party library like Glide that can support GIFs.
I made a quick example app for displaying a GIF from Giphy's API here.
Hope that helps
The general sketch of the solution is to use employ custom View which draws asks a Movie to draw itself to the Canvas periodically.
The first step is building the Movie instance. There is factory called decodeStream that can make a movie given an InputStream but it isn't enough to use the stream from a UrlConnection. If you try this you will get an IOException when the movie loader tries to call reset on the stream. The hack, unfortunate as it is, is to use a separated BufferedInputStream with a manually-set mark to tell it to save enough data that reset won't fail. Luckily, the URLConnection can tell us how much data to expect. I say this hack is unfortunate because it effectively requires the entire image to be buffered in memory (which is no problem for desktop apps, but it is a serious issue on a memory-constrained mobile device).
Here is a snip of the Movie setup code:
URL url = new URL(gifSource);
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
bis.mark(conn.getContentLength());
Movie movie = Movie.decodeStream(bis);
bis.close();
Next, you need to create a view that will display this Movie. A subclass of View with a custom onDraw will do the trick (assuming it has access to the Movie you created with the previous code).
#Override protected void onDraw(Canvas canvas) {
if(movie != null) {
long now = android.os.SystemClock.uptimeMillis();
int dur = Math.max(movie.duration(), 1); // is it really animated?
int pos = (int)(now % dur);
movie.setTime(pos);
movie.draw(canvas, x, y);
}
}
The view won't trigger itself to be redrawn without help, and blindly calling invalidate() at the end of onDraw is just an energy waste. In another thread (probably the one you used to download the image data), you can post messages to the main thread, asking for the view to be invalidated at a steady (but not insane) pace.
Handler handler = new Handler();
new Thread() {
#Override public void run() {
// ... setup the movie (using the code from above)
// ... create and display the custom view, passing the movie
while(!Thread.currentThread().isInterrupted()) {
handler.post(new Runnable() {
public void run(){
view.invalidate();
}
});
try {
Thread.sleep(50); // yields 20 fps
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}.start();
hope it will help you to understand
Related
I am working on an app that uses a Recyclerview to display mp3 files, providing its cover art image along with other info. It works but is slow once it starts dealing with a dozen or more cover arts to retrieve, as I am currently doing this from the id3 on the main thread, which I know is not a good idea.
Ideally, I would work with placeholders so that the images can be added as they become available. I've been looking into moving the retrieval to a background thread and have looked at different options: AsyncTask, Service, WorkManager. AsyncTask seems not to be the way to go as I face memory leaks (I need context to retrieve the cover art through MetadataRetriever). So I am leaning away from that. Yet I am struggling to figure out which approach is best in my case.
From what I understand I need to find an approach that allows multithreading and also a means to cancel the retrieval in case the user has already moved on (scrolling or navigating away). I am already using Glide, which I understand should help with the caching.
I know I could rework the whole approach and provide the cover art as images separately, but that seems a last resort to me, as I would rather not weigh down the app with even more data.
The current version of the app is here (please note it will not run as I cannot openly divulge certain aspects). I am retrieving the cover art as follows (on the main thread):
static public Bitmap getCoverArt(Uri medUri, Context ctxt) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(ctxt, medUri);
byte[] data = mmr.getEmbeddedPicture();
if (data != null) {
return BitmapFactory.decodeByteArray(data, 0, data.length);
} else {
return null;
}
}
I've found many examples with AsyncTask or just keeping the MetaDataRetriever on the main thread, but have yet to find an example that enables a dozen or more cover arts to be retrieved without slowing down the main thread. I would appreciate any help and pointers.
It turns out it does work with AsyncTask, as long as it is not a class onto itself but setup and called from a class with context. Here is a whittled down version of my approach (I am calling this from within my Adapter.):
//set up titles and placeholder image so we needn't wait on the image to load
titleTv.setText(selectedMed.getTitle());
subtitleTv.setText(selectedMed.getSubtitle());
imageIv.setImageResource(R.drawable.ic_launcher_foreground);
imageIv.setAlpha((float) 0.2);
final long[] duration = new long[1];
//a Caching system that helps reduce the amount of loading needed. See: https://github.com/cbonan/BitmapFun?files=1
if (lruCacheManager.getBitmapFromMemCache(selectedMed.getId() + position) != null) {
//is there an earlier cached image to reuse? imageIv.setImageBitmap(lruCacheManager.getBitmapFromMemCache(selectedMed.getId() + position));
imageIv.setAlpha((float) 1.0);
titleTv.setVisibility(View.GONE);
subtitleTv.setVisibility(View.GONE);
} else {
//time to load and show the image. For good measure, the duration is also queried, as this also needs the setDataSource which causes slow down
new AsyncTask<Uri, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(Uri... uris) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(ctxt, medUri);
byte[] data = mmr.getEmbeddedPicture();
Log.v(TAG, "async data: " + Arrays.toString(data));
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
duration[0] = Long.parseLong(durationStr);
if (data != null) {
InputStream is = new ByteArrayInputStream(mmr.getEmbeddedPicture());
return BitmapFactory.decodeStream(is);
} else {
return null;
}
}
#Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
durationTv.setVisibility(View.VISIBLE);
durationTv.setText(getDisplayTime(duration[0], false));
if (bitmap != null) {
imageIv.setImageBitmap(bitmap);
imageIv.setAlpha((float) 1.0);
titleTv.setVisibility(View.GONE);
subtitleTv.setVisibility(View.GONE);
} else {
titleTv.setVisibility(View.VISIBLE);
subtitleTv.setVisibility(View.VISIBLE);
}
lruCacheManager.addBitmapToMemCache(bitmap, selectedMed.getId() + position);
}
}.execute(medUri);
}
I have tried working with Glide for the caching, but I haven't been able to link the showing/hiding of the TextViews to whether there is a bitmap. In a way though, this is sleeker as I don't need to load the bulk of the Glide-library. So I am happy with this for now.
I have an activity in which the user presses a button which fetches a JSON response from a URL, and then downloads and saves all of the image URLs in that JSON. The downloading takes place in a separate class which extends Thread:
downloadButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DownloadTask task = new DownloadTask(handler);
task.start();
}
});
handler is an inner static Handler that holds a WeakReference to the activity (used for displaying progress).
In the DownloadTask:
public DownloadTask(Handler handler) {
this.handler = handler;
}
#Override
public void run() {
String jsonString = // gets JSON from server
urlsToDownload = new HashSet<String>();
// do some stuff with the JSON to put each URL into the Set
for (Iterator<String> i = urlsToDownload.iterator(); i.hasNext(); ) {
String urlString = i.next();
// the following takes place in two static method calls,
// but I've laid it all out here for easier interpretation.
// I'm also removing all try/catch blocks, if (x != null) checks etc
// first download the image from the web
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.connect();
BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
Bitmap bitmap = BitmapFactory.decodeStream(bis);
bis.close() // (done in try-with-resource)
connection.disconnect();
// then save the image on the device
File file = new File(App.context.getFilesDir(), "my/file/name.jpg");
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
fos.close() // (done in try-with-resource)
// make a Bundle, add some progress info and send it in a Message
handler.handleMessage(msg);
}
}
My problem is that this is using a very large amount of memory. When looking at the memory monitor in Android Studio, it spikes up to ~95MB when downloading/saving each image (~1.7MB). I used the allocation tracker to take a close look, and there is this one line which bothers me:
Can anyone help me figure out why this is happening? As far as I know, this is a "standard" way to download images in Android.
You make intermediate Bitmaps from your files. That takes a lot of memory. Do not use Bitmaps but save the image bytes directly to file. You are probably downloading a jpg file i think.
Just make a loop in which you read chunks from the input stream and write them to the file output stream.
I'm using muPDF for reading PDFs in my application. I don't like its default animation (Switching horizontally). In other side i found this brilliant library for curl effect on images, and this project for flip-flap effect on layouts.
In curl sample project, in CurlActivity, all of data are images and set in PageProvider like this:
private class PageProvider implements CurlView.PageProvider {
// Bitmap resources.
private int[] mBitmapIds = { R.drawable.image1, R.drawable.image2,
R.drawable.image3, R.drawable.image4};
And use it like this:
private CurlView mCurlView;
mCurlView = (CurlView) findViewById(R.id.curl);
mCurlView.setPageProvider(new PageProvider());
And CurlView extends from GLSurfaceView and implements View.OnTouchListener, CurlRenderer.Observer
But in muPDF if i'm not mistaken, data are in core object. core is instance of MuPDFCore. And using it like this:
MuPDFReaderView mDocView;
MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView();
mDocView.setAdapter(new MuPDFPageAdapter(this, this, core));
MuPDFReaderView extends ReaderView and ReaderView extends AdapterView<Adapter> and implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable.
My question is where how can I using curl effect in muPDF? Where should I get pages one by one and converting them to bitmaps? and then changing aspects of the Adapter in muPDF to CurlView.
In flip-flap sample project, in FlipHorizontalLayoutActivity (I like this effect too), we have these:
private FlipViewController flipView;
flipView = new FlipViewController(this, FlipViewController.HORIZONTAL);
flipView.setAdapter(new TravelAdapter(this));
setContentView(flipView);
And FlipViewController extends AdapterView<Adapter>, and data set in TravelAdapter that extends BaseAdapter.
No one has done this before? Or can help me to do that?!
EDIT:
I found another good open source PDF reader with curl effect called fbreaderJ. its developer says "An additional module that allows to open PDF files in FBReader. Based on radaee pdf library."
I got confused! cause radaeepdf is closed source and downloadable project is just for demo and inserted username and password is for this package.
People want to change whole fbreader project such as package name.
Another issue for make me confused is where is this additional module source code?!
Anyway, if someone wants to help me, fbreader has done it very well.
EDIT:
I talked to Robin Watts, who developed muPDF (or one of developers), and he said:
Have you read platform/android/ClassStructure.txt ? MuPDF is
primarily a C library. The standard api is therefore a C one. Rather
than exposing that api exactly as is to Java (which would be the
nicest solution, and something that I've done some work on, but have
not completed due to lack of time), we've implemented MuPDFCore to
wrap up just the bits we needed. MuPDFCore handles opening a PDF file,
and getting bitmaps from it to be used in views. or rather, MuPDFCore
returns 'views', not 'bitmaps'. If you need bitmaps, then you're going
to need to make changes in MuPDFCore.
There are too many errors when changing a little part of MuPDFReaderView class. I get confused! These are related to each other.
Please answer more precisely.
EDIT:
And bounty has expired.
If the muPDF does not support rendering to a bitmap, you have no other choice than rendering to a regular view and take a screen dump to a bitmap like this:
View content = findViewById(R.id.yourPdfView);
Bitmap bitmap = content.getDrawingCache();
Then use this bitmap as input to your other library.
Where should i get pages one by one and converting them to bitmaps?
In our application (newspaper app) we use MuPDF to render PDFs.
The workflow goes like this:
Download PDF file (we have one PDF per newspaper page)
Render it with MuPDF
Save the bitmap to the filesystem
Load the Bitmap from filesystem as background image to a view
So, finally, what we use is MuPDFCore.java and its methods drawPage(...) and onDestroy()
Is this what you want to know or do i miss the point?
EDIT
1.) I think it is not necessary to post code how to download a file. But after downloading i add a RenderTask (extends from Runnable) to a Renderqueue and trigger that queue. The RenderTask needs some information for rendering:
/**
* constructs a new RenderTask instance
* #param context: you need Context for MuPdfCore instance
* #param pageNumber
* #param pathToPdf
* #param renderCallback: callback to set bitmap to the view after
* rendering
* #param heightOfRenderedBitmap: this is the target height
* #param widthOfRenderedBitmap: this is the target width
*/
public RenderTask (Context context, Integer pageNumber, String pathToPdf, IRenderCallback,
renderCallback, int heightOfRenderedBitmap,
int widthOfRenderedBitmap) {
//store things in fields
}
2.) + 3.) The Renderqueue wraps the RenderTask in a new Thread and starts it. So the run-method of the RenderTask will be invoked:
#Override
public void run () {
//do not render it if file exists
if (exists () == true) {
finish();
return;
}
Bitmap bitmap = render();
//if something went wrong, we can't store the bitmap
if (bitmap == null) {
finish();
return;
}
//now save the bitmap
// in my case i save the destination path in a String field
imagePath = save(bitmap, new File("path/to/your/destination/folder/" + pageNumber + ".jpg"));
bitmap.recycle();
finish();
}
/**
* let's trigger the callback
*/
private void finish () {
if (renderCallback != null) {
// i send the whole Rendertask to callback
// maybe in your case it is enough to send the pageNumber or path to
// renderend bitmap
renderCallback.finished(this);
}
}
/**
* renders a bitmap
* #return
*/
private Bitmap render() {
MuPDFCore core = null;
try {
core = new MuPDFCore(context, pathToPdf);
} catch (Exception e) {
return null;
}
Bitmap bm = Bitmap.createBitmap(widthOfRenderedBitmap, heightOfRenderedBitmap, Config.ARGB_8888);
// here you render the WHOLE pdf cause patch-x/-y == 0
core.drawPage(bm, 0, widthOfRenderedBitmap, heightOfRenderedBitmap, 0, 0, widthOfRenderedBitmap, heightOfRenderedBitmap, core.new Cookie());
core.onDestroy();
core = null;
return bm;
}
/**
* saves bitmap to filesystem
* #param bitmap
* #param image
* #return
*/
private String save(Bitmap bitmap, File image) {
FileOutputStream out = null;
try {
out = new FileOutputStream(image.getAbsolutePath());
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
return image.getAbsolutePath();
} catch (Exception e) {
return null;
}
finally {
try {
if (out != null) {
out.close();
}
} catch(Throwable ignore) {}
}
}
}
4.) I think it is not necessary to post code how to set a bitmap as background of a view
I have an app where I have one banner in the top with News, when I want to put other news I need to open the code and change the resource .jpg and the Link. There is a way to change the banner and the Link (or at least the banner) without modifing the code? Idk maybe uploading it to a webpage or something like this.
thanks
My suggestion would be to upload a banner.jpg to a server that your app can access and dynamically load. This would prevent having to update your app every time you want to change the banner, and makes it cleaner (no excessive Google Play updates). To do actually load the image you can use this code:
ImageView image1 = (ImageView) findViewById(R.id.mybanner);
new Thread(new Runnable(){//create a new thread so we can do network operations
#Override
public void run() {//main thread function
try {//attempt to do network stuff
URL url = new URL("http://your-hosting-site.com/banner.jpg");//create aURL object with the path to your banner
HttpURLConnection con = (HttpURLConnection) url.openConnection();//create the connection object from the url
con.setReadTimeout(15000);
con.setConnectTimeout(15000);
con.setRequestMethod("GET");
con.setDoInput(true);
con.connect();//connect to the server
InputStream is = con.getInputStream();//get the stream so we can read the image
Drawable d = Drawable.createFromStream(is, "MyBanner");//create a drawable from the image
Bitmap bmp = ((BitmapDrawable) d).getBitmap();//create a bitmap from the drawable
final Drawable dS = new BitmapDrawable(Bitmap.createScaledBitmap(bmp, 192, 192, true));//scale it to whatever size you need
con.disconnect();//disconnect now that we're done
runOnUiThread(new Runnable(){//run UI update code on the main thread
#Override
public void run() {
image1.setImageDrawable(dS);//set the imageview to the banner we downloaded
}
});
} catch (MalformedURLException e) {//catch url error
e.printStackTrace();
} catch (IOException e) {//catch io error when downloading
e.printStackTrace();
}
}
}).start();//run the thread
Change "http://your-hosting-site.com/banner.jpg" (line 6) to wherever you uploaded the .jpg, R.id.mybanner (line 1) to the id of your ImageView, and "MyBanner" (line 14) to whatever you want to call the image.
You might want to save your banner to the phone and only check after X days/hours for an update to save data, but that is up to you.
I have a large list of objects, all of whom have a path to their image "ex. http://www.google.com/image.jpg" and I need to download the image and save the drawable to the object..
I was using AsyncTask, but even if I use my own threads I always end up with 'OutOfMemoryError' at some arbitrary point in the list. The images are never larger than 82Kb (Is this too big for android tablets?) in size, but I think the sheer number of images is causing the process as a whole to fail.
Here is what I'm currently doing.
class DownloadImageTask extends AsyncTask<ArrayList<Item>, Void, Void> {
private static int num =1;
#Override
protected Void doInBackground(ArrayList<Item>... items) {
try {
if(items.length == 0)
return null;
HttpURLConnection connection;
InputStream input;
for(ArrayList<Item> itemlist : items) {
for(Item i : itemlist) {
Log.d(JusTouchMenu.TAG,"[Item]Image request to url:"+i.getImagePath());
try {
connection = (HttpURLConnection)new URL(i.getImagePath()).openConnection();
connection.setRequestProperty("User-agent","Mozilla/4.0");
connection.connect();
input = connection.getInputStream();
i.setImage(new BitmapDrawable(BitmapFactory.decodeStream(input)));//Requires a drawable
connection.disconnect();
} catch(Exception e) {Log.e(JusTouchMenu.TAG,"[Item]Unable to download image # '"+i.getImagePath()+"'",e);}
Log.v(JusTouchMenu.TAG, "[Item]Image decoded # '"+i.getImagePath()+"' #"+num++);
for(Tag pt : i.tags()) {
Log.d(JusTouchMenu.TAG,"[Item->Tag]Image request to url:"+pt.getImagePath());
try {
connection = (HttpURLConnection)new URL(pt.getImagePath()).openConnection();
connection.setRequestProperty("User-agent","Mozilla/4.0");
connection.connect();
input = connection.getInputStream();
pt.setImage(new BitmapDrawable(BitmapFactory.decodeStream(input))); //Requires a drawable
connection.disconnect();
} catch(Exception e) {
Log.e(JusTouchMenu.TAG,"[Item->Tag]Unable to download image # '"+i.getImagePath()+"'",e);
}
Log.v(JusTouchMenu.TAG, "[Item->Tag]Image decoded # '"+pt.getImagePath()+"' #"+num++);
}
}
}
} catch(Exception e) {
Log.e(JusTouchMenu.TAG,"Error decoding image inside AsyncTask",e);
}
return null;
}
Thanks!
How many of these images are you trying to get at once? How are you using them?
You should probably consider using the capabilities of Gallery or GridView and thereby only be loading the images that are in view at the moment. You can even get fancy and cache them so scrolling is nice and smooth. Trying to load all of the images into an ArrayList when youre not likely to be able to display them all at once is not a mobile friendly approach.
Here are some decent references to caching approaches but you should probably get started with a basic gallery or grid view loading as needed and then add the caching on.
Lazy load of images in ListView
http://code.google.com/p/libs-for-android/wiki/ImageLoader