Lazy loading of image in ImageSpan - android

I want to build custom ImageSpan which set up default picture at the beginning of fetching data and loaded picture when fetching finished.
Example of code:
public class CustomImageSpan extends ImageSpan {
private Drawable mDrawable;
#Override
public Drawable getDrawable() {
if (mDrawable == null) {
mDrawable = defaultDrawable;
Futures.addCallback(
FetchImage.submit(
new FutureCallback<Image>() {
#Override
public void onSuccess(Image result) {
Bitmap bitmap = result.getBitmap();
mDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
//!Rerender span with new bitmap.
}
}, mExecutor);
}
return mDrawable;
}
How to notify span that loading has finished and it`s time to get new bitmap instead of default??

You could try and use a handler to proceed to the next task once the current one has completed
Handler xHandler=new Handler(){
#Override
public void handleMessage(Message message){
switch (message.what){
case SOME_OPERATION:
getImage(); //the method you are using
break;
}
}
};
run the handleer in a seprate thread as below
#Override
public void run() {
xHandler.sendMessage(Message.obtain(xHandler, SOME_OPERATION));
}

Related

Picasso Target has been garbage collected

Good day.I have an google map with cluster manager.Simple one,where i use the cluster to draw markers grouped or not.Anyway i got an method callback from cluster manager which is the Cluster item render one.Inside that callback i am applying custom image to the marker:The user image inside marker.I found Picasso to be the best to handle bitmap loading and at the same time got me lots of headache.I am using Target class from Picasso to initiate the bitmap callbacks:OnPreLoad,OnFail,OnBitmapLoaded.The issue is that on first cluster item render the onBitmapLoaded not called and generally it is never gets called unless it has been touched second time.On first time nothing happens,no callback is triggered except OnPreLoad and by googling i found that the great Picasso holds weak reference to the class.I tried all the examples of the google:Making Target reference strong(getting the initialazation of class out of method and init the class inside my class like the follows)
#Override
protected void onClusterItemRendered(MarkerItem clusterItem, Marker marker) {
mMarker = marker;
mMarkerItem = clusterItem;
Picasso.with(mContext).load(clusterItem.getImageUrl()).transform(new CircleTransformation()).into(target);
}
private Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Log.d(TAG, "onBitmapLoaded: ");
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.d(TAG, "onBitmapFailed: ");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
Log.d(TAG, "onPrepareLoad: ");
}
};
#Override
protected void onBeforeClusterItemRendered(MarkerItem item, MarkerOptions markerOptions) {
markerOptions.title(item.getTitle());
markerOptions.icon(item.getIcon());
}
At this point i get the same result....Sometimes the bitmap loaded and sometimes not.Mostly not...
Anyway i have tried to implement the interface class to my own class as follows:
public class PicassoMarkerView implements com.squareup.picasso.Target {
private static final String TAG = "MarkerRender";
private Bitmap mMarkerBitmap;
private ClusterManager<MarkerItem> mClusterManager;
private MarkerItem mMarkerItem;
private Marker mMarker;
public PicassoMarkerView() {
}
#Override
public int hashCode() {
return mMarker.hashCode();
}
#Override
public boolean equals(Object o) {
if (o instanceof PicassoMarkerView) {
Marker marker = ((PicassoMarkerView) o).mMarker;
return mMarker.equals(marker);
} else {
return false;
}
}
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap,
mMarkerBitmap.getWidth() - 15, (int) (mMarkerBitmap.getHeight() / 1.5 - 15),
false);
mMarker.setIcon(BitmapDescriptorFactory.fromBitmap(overlay(mMarkerBitmap, scaledBitmap, 8, 7)));
Log.d(TAG, "onBitmapLoaded: ");
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.d(TAG, "onBitmapFailed: ");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
Log.d(TAG, "onPrepareLoad: ");
}
private Bitmap overlay(Bitmap bitmap1, Bitmap bitmap2, int left, int top) {
Bitmap res = Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(),
bitmap1.getConfig());
Canvas canvas = new Canvas(res);
canvas.drawBitmap(bitmap1, new Matrix(), null);
canvas.drawBitmap(bitmap2, left, top, null);
return res;
}
public void setMarkerBitmap(Bitmap markerBitmap) {
this.mMarkerBitmap = markerBitmap;
}
public void setClusterManager(ClusterManager<MarkerItem> clusterManager) {
this.mClusterManager = clusterManager;
}
public void setMarkerItem(MarkerItem markerItem) {
this.mMarkerItem = markerItem;
}
public void setMarker(Marker marker) {
this.mMarker = marker;
}
}
Unfortunatally this is not working either...Same result...So please dear friends can you give me an working example of this?As far as i could google,the issue mostly happens to the user which try to do this inside loop and my onClusterItemRender some sort of loop lets say as it is triggered every time marker is visible to user,so yeah it is triggered several times and as fast as loop so give me some idea please and help me out...
Important to mention that i do not need to use methods from picasso like fetch(),get() as they are not necessary and not fitting the purpose of the app.
I encountered similar issue and holding reference to the target didn't help at all.
The purpose of my project was to use 2 different image downloading api's to show an images gallery and to give the user the ability to choose which api to use.
Beside Picasso I used Glide, and I was amazed by the results, Glide's api worked flawlessly in every aspect wile Picasso gave me hell (that was my first time using Glide, I usually used Picasso so far, seems like today it's gonna change ^^ ).
So my suggestion to you is:
Use glide over Picasso (no such weak reference on their target).
Since I had to use both libraries I ended up using get() in an handler, not sure if it will help you but it solved my problem:
handlerThread = new HandlerThread(HANDLER_THREAD_NAME);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
handler.post(new Runnable() {
#Override
public void run() {
Bitmap bitmap = null;
try {
bitmap = picasso.with(appContext).load(url).get();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bitmap != null) {
//do whatever you wanna do with the picture.
//for me it was using my own cache
imageCaching.cacheImage(imageId, bitmap);
}
}
}
});

All threads get suspended

I am building an app which will continuously get screenshots of my laptop screen and transfer it to my android app but there is some problem within the while loop, when I put a for loop to a limit then my program runs but as it goes till infinity or I replace it with infinite while loop my code suspends all the threads and app crash dueto memory allocation problem, please suggest me to execute my code infinite times so that there are continuous screenshots displayed.
Thank You.
Here is my code
public class ScreenActivity extends AppCompatActivity {
ImageView img;
int width,height;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen);
img=(ImageView)findViewById(R.id.imageView);
Display display = getWindowManager().getDefaultDisplay();
width = display.getWidth();
height = display.getHeight();
// while (true)
for (int i=0;i<100;i++)
new GetImg().execute();
}
Bitmap imgscr;
public class GetImg extends AsyncTask<Object,Void,Bitmap> {
#Override
protected Bitmap doInBackground(Object[] params) {
Socket client= null;
try {
client = new Socket("192.168.1.5",6767);
InputStream in=client.getInputStream();
imgscr=Bitmap.createScaledBitmap(BitmapFactory.decodeStream(in), width, height, false);
} catch (Exception e) {
e.printStackTrace();
}
return imgscr;
}
#Override
protected void onPostExecute(Bitmap bm)
{
img.setImageBitmap(bm);
}
}
}
#m0skit0 commented the actual reason of getting the ANR. You're out of your run-time memory when you're creating threads in an infinite loop. I'm pretty confused about your purpose though. I think you need to get the screenshots one after one and if this is the case, you can simply add a listener to the AsyncTask and get the callback when the screenshot is downloaded fully.
So if I've understood correctly, you need to declare an interface like this.
public interface DownloadCompletedListener {
public void onDownloadComplete(String result);
}
Now you need to implement the interface in your Activity like this
public class ScreenActivity extends AppCompatActivity implements DownloadCompletedListener {
private GetImg getImageTask;
private Bitmap imageBitmap;
#Override
public void onDownloadComplete(String result) {
if(result.equals("SUCCESS")) {
// Set the image now
img.setImageBitmap(imageBitmap);
// Start next download here
getImageTask = new GetImg();
getImageTask.mListener = this;
getImageTask.execute();
} else {
// Do something
}
}
}
You need to modify your AsyncTask a bit. You need to declare the DownloadCompletedListener.
public class GetImg extends AsyncTask<Object,Void,Bitmap> {
private DownloadCompletedListener mListener;
#Override
protected Bitmap doInBackground(Object[] params) {
Socket client= null;
try {
client = new Socket("192.168.1.5",6767);
InputStream in=client.getInputStream();
imgscr=Bitmap.createScaledBitmap(BitmapFactory.decodeStream(in), width, height, false);
} catch (Exception e) {
e.printStackTrace();
}
return imgscr;
}
#Override
protected String onPostExecute(Bitmap bm)
{
imageBitmap = bm;
mListener.onDownloadComplete("SUCCESS");
}
}
So your onCreate function will look like this now
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen);
img=(ImageView)findViewById(R.id.imageView);
Display display = getWindowManager().getDefaultDisplay();
width = display.getWidth();
height = display.getHeight();
// Start downloading image here. Remove the loop
getImageTask = new GetImg();
getImageTask.mListener = this;
getImageTask.execute();
}

How to make Picasso/Glide work with Html.ImageGetter for caching images?

Thanks for all the effort #Budius has made.
Most part of image work of my app could be handled by Picasso/Glide, however, some images are displayed in a TextView by Html.fromHtml. And the images in TextView are also used frequently.
However, I don't know how to implement getDrawable() method with Picasso/Glide for the ImageGetter passed to Html.fromHtml. Is it possible to share the same cache of Picasso/Glide for these pictures in TextView and other bitmaps?
Or should I use an custom LruCache instead to cache these pictures form ImageGetter separately? Will this way increase the risk of an OOM error? And I think it creates unnecessary workload to use 2 different systems for processing images.
Update: I tried to use .get() of Picasso, but the doc says
/**
* The result of this operation is not cached in memory because the underlying
* {#link Cache} implementation is not guaranteed to be thread-safe.
*/
So the cache is not used in this case.
Update:
The answer of #Budius is right, but code of setting bounds for the Drawable is missing, which leaves the Drawable not displayed in the TextView. So I modified the code in the DrawableWrapper class into:
public void setWrappedDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
mDrawable.setBounds(0,0,mDrawable.getIntrinsicWidth(),mDrawable.getIntrinsicHeight());
drawable.setCallback(this);
}
}
Update:, the problem is still unsolved. If you implement the solution forementioned, there are some strange behaviors for the image in TextView. Sometimes the image could not be refreshed unless you switch to another app and switch back, and the position of image is severely incorrect.
Update: I have post all the code for test below. There're still some bugs. Without a placeholder, it still throws an NPE. With a placeholder, the behavior is very strange. The first time I enter TestActivity, it shows the placeholder but it won't change into the downloaded pic. But after I switch to another app or press a back button and enter TestActivity again, the pic is displayed(maybe because it's in the cache?).
And also the size of pic is right but the place is still not left for the image. And if I call mDrawable.setBounds(getBounds()); instead of mDrawable.setBounds(0,0,getIntrinsicWidth(),getIntrinsicHeight());, it will not be displayed.
DrawableWrapper
public class DrawableWrapper extends Drawable implements Drawable.Callback {
private Drawable mDrawable;
public DrawableWrapper(Drawable drawable) {
setWrappedDrawable(drawable);
}
#Override
public void draw(Canvas canvas) {
mDrawable.draw(canvas);
}
#Override
public int getIntrinsicWidth() {
return 384;
}
#Override
public int getIntrinsicHeight() {
return 216;
}
//... other delegation methods are omitted
public void setWrappedDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
mDrawable.setBounds(0,0,getIntrinsicWidth(),getIntrinsicHeight());
drawable.setCallback(this);
}
}
}
PicassoTargetDrawable
public class PicassoTargetDrawable extends DrawableWrapper
implements Target {
private Context context;
public PicassoTargetDrawable(Context context) {
super(new ColorDrawable(0));
// use application context to not leak activity
this.context = context.getApplicationContext();
}
public void onBitmapFailed(Drawable errorDrawable) {
setWrappedDrawable(errorDrawable);
invalidateSelf();
}
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
setWrappedDrawable(new BitmapDrawable(context.getResources(), bitmap));
context = null;
invalidateSelf();
}
public void onPrepareLoad(Drawable placeHolderDrawable) {
setWrappedDrawable(placeHolderDrawable);
invalidateSelf();
}
}
TestActivity
public class TestActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
textView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
setContentView(textView);
String html = "<div>test<br/>" +
"<img src=\"http://i2.cdn.turner.com/money/dam/assets/150910165544-elon-evo-open-still-384x216.png\"></img>" +
"<br/>/test</div>";
textView.setText(Html.fromHtml(html, new Html.ImageGetter() {
#Override
public Drawable getDrawable(String source) {
PicassoTargetDrawable d = new PicassoTargetDrawable(TestActivity.this);
Picasso.with(TestActivity.this)
.load(source)
//add placeholder here
.into(d);
return d;
}
}, null));
}
}
My Suggestion is to return a wrap drawable. And keep using Picasso to download the image.
On the following link you can find an DrawableWrapper, it's from Googles support library, but it's not part of the public docs, so I would just copy the whole code into your project https://android.googlesource.com/platform/frameworks/support/+/master/v7/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java
And then you create a PicassoTargetDrawable from it.
public class PicassoTargetDrawable extends DrawableWrapper
implements Picasso.Target {
private Context context;
public PicassoTargetDrawable(Context context) {
super(new ColorDrawable(0));
// use application context to not leak activity
this.context = context.getApplicationContext();
}
public void onBitmapFailed(Drawable errorDrawable) {
setWrappedDrawable(errorDrawable);
invalidateSelf();
}
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
setWrappedDrawable(new BitmapDrawable(context.getResources(), bitmap));
context = null;
invalidateSelf();
}
public void onPrepareLoad(Drawable placeHolderDrawable) {
setWrappedDrawable(placeHolderDrawable);
invalidateSelf();
}
}
Then it's just a matter of loading it up
public void Drawable getDrawable(String source) {
PicassoTargetDrawable d = new PicassoTargetDrawable(context);
Picasso.with(context)
.load(source)
..... add here onError and placeholder drawables
.into(d);
return d;
}
PS.:
I wrote all this without looking up too much, there will probably be a few typos and a few issues to sort it out, but it's certainly enough for you to understand the concept.
update:
Just correcting your code.
The TextView already told the WrapDrawable the Bounds it should use. If you're telling the new mDrawable that it can use whatever size it wants, it will use whatever size it wants. So instead of passing its own intrinsic width/height, you should pass the size that was give to the WrapDrawable
public void setWrappedDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
mDrawable.setBounds(getBounds());
drawable.setCallback(this);
}
}

Add AnimationDrawable (PNG Sequence) to Canvas?

I have started venturing into a bit of Canvas stuff and managed to add some statis PNG files to it.
Now I'd like to load in a PNG sequence, which I believe is an AnimationDrawable (rather then Bitmap)
I have written the XML file for the animation but then I am stumped.
I cannot find any examples of people adding PNG sequences to a Canvas object.
here is sample:
public class MainActivity extends Activity {
private static final int FRAME_DELAY = 200; // in ms
private ArrayList<Bitmap> mBitmaps;
private final AtomicInteger mBitmapIndex = new AtomicInteger();
private View mView;
private Thread mThread;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// load resources
mBitmaps = new ArrayList<Bitmap>();
for(int resId : new int[]{
// resource ids here
R.drawable.ic_launcher,
R.drawable.ddms_128,
R.drawable.ddms_icon
}){
mBitmaps.add(BitmapFactory.decodeResource(getResources(), resId));
}
// create View and implement 'draw'
ViewGroup root = (ViewGroup) findViewById(R.id.root);
root.addView(mView = new View(this){
#Override
public void draw(Canvas canvas) {
canvas.drawBitmap(mBitmaps.get(Math.abs(mBitmapIndex.get() % mBitmaps.size())), 10, 10, null);
super.draw(canvas);
}
});
}
#Override
protected void onStart() {
super.onStart();
mThread = new Thread(){
#Override
public void run() {
// wait and invalidate view until interrupted
while(true){
try {
Thread.sleep(FRAME_DELAY);
} catch (InterruptedException e) {
e.printStackTrace();
break; // get out if interrupted
}
mBitmapIndex.incrementAndGet();
mView.postInvalidate();
}
}
};
mThread.start();
}
#Override
protected void onStop() {
mThread.interrupt();
super.onStop();
}
}
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</RelativeLayout>
Canvas allows to draw line/shapes/bitmaps.
You managed to draw one bitmap.
Animation is just drawing one bitmap from bunch of them.
Canvas does nothing with drawables.
The logic neither related to Canvas nor to drawables.
Draw the first frame, then use a Handler to post delayed Message that when handled moves to the next frame and calls invalidate().
What you need to do is:
Step #1. Override VisualizerView.verifyDrawable(Drawable who)
#Override
protected boolean verifyDrawable(Drawable who) {
return true;
}
Step #2. Modify MainActivity.addAnimationRenderer()
private void addAnimationRenderer() {
final AnimationDrawable anim = (AnimationDrawable) getResources().getDrawable(R.drawable.png1);
AnimationRenderer animRenderer = new AnimationRenderer(anim);
mVisualizerView.addRenderer(animRenderer);
anim.setCallback(mVisualizerView);
mVisualizerView.post(new Runnable() {
#Override
public void run() {
anim.start();
}
});
}
Step #3. Modify AnimationRenderer: delete
mBitmap.setCallback(null);
mBitmap.start();

Passing parameters into AsyncTask

Hi so i'm trying to grab a image from a url link via AsyncTask, the function to grab the image itself work fine. but what i trying to do is to pass the src variable into a asyncTask which seems to be not working for me. the return will be blank.
here is the code:
private AsyncTask<String, Void, Drawable> task2;
Drawable profile;
public Drawable getProfile(String src){
task2 = new AsyncTask<String, Void, Drawable>() {
ProgressDialog dialog2;
InputStream is;
Drawable d;
#Override
protected void onPreExecute(){
dialog2 = new ProgressDialog(Thoughts.this, ProgressDialog.STYLE_SPINNER);
dialog2.setMessage("Loading Data...");
dialog2.setCancelable(false);
dialog2.setCanceledOnTouchOutside(false);
dialog2.show();
}
#Override
protected Drawable doInBackground(String... src) {
try
{
is = (InputStream) new URL(src[0]).getContent();
d = Drawable.createFromStream(is, "src name");
return d;
}catch (Exception e) {
e.toString();
return null;
}
}
#Override
protected void onPostExecute(Drawable result2) {
profile = result2;
dialog2.dismiss();
}
};
task2.execute(src);
return profile;
}
and i call it like this at the onCreate();
Drawable p4 = getProfile("http://..../xyz.jpg");
Drawable p5 = getProfile("http://..../xyz.jpg");
ImageView thoughtsProfilePic =(ImageView) findViewById(R.id.ivProfilePicData);
ImageView thoughtsProfilePic1 =(ImageView) findViewById(R.id.ivProfilePicData1);
thoughtsProfilePic.setImageDrawable(p4);
thoughtsProfilePic1.setImageDrawable(p5);
AsyncTask help you do an asynchronous job. In your code, I can see you return Drawable right after calling it. But at that moment, the your asynctask hasn't completed yet and drawable still null.
task2.execute(src);
return profile;
If you want set drawable resource when complete job in asynctask, just put your ImageView into your method. It should be:
public void getProfile(String src, final ImageView v){
#Override
protected void onPostExecute(Drawable result2) {
// set drawable for ImageView when complete.
v.setImageDrawable(result2);
dialog2.dismiss();
}
task2.excute(src);
//do not need return anything.
}
Use it:
ImageView thoughtsProfilePic =(ImageView) findViewById(R.id.ivProfilePicData);
getProfile("http://..../xyz.jpg", thoughtsProfilePic );
Hope this help.
Update:
There is no way to return value from asynchronous method directly, here is another choice.
First, create an interface to notify when complete job.
public interface INotifyComplete{
public void onResult(Drawable result);
}
Then your activity class should look like:
public class YourActivity extends Activity implement INotifyComplete{
private Drawable res1;
private Drawable res2;
public void onResult(Drawable result){
if(result == res1){
// do something with resource 1
ImageView thoughtsProfilePic =(ImageView) findViewById(R.id.ivProfilePicData);
thoughtsProfilePic.setImageDrawable(result);
}
if(result == res2){
// do something with resource 2
}
}
void someMethod(){
// you can use this way to call
getProfile("http://..../xyz.jpg", res1, YourActivity.this);
//or this
getProfile("http://..../xyz.jpg", res2, new INotifyComplete(){
public void onResult(Drawable result){
// result is res2, so don't need to check
}
});
}
public void getProfile(String src, final Drawable res, final INotifyComplete notify){
//don't need store asynctask instance
AsyncTask<String, Void, Drawable> task2 = new AsyncTask<String, Void, Drawable>(){
// do something ...
protected void onPostExecute(Drawable result2) {
dialog2.dismiss();
// you will set the value to your drawable then notify it when complete job
res = result2;
notify.onResult(res);
}
}
task2.excute();
}
}
write a public member function in your activity which returns drawable and just call the function in doInBackground() method of your asyncTask class.
Drawable downloadImage(){
//write code here to download image so you can return any dataType
}
now just call this function in doInBackground() method and save returned result in some variable of your activity.
like
void doInBackground(){
drawableVariable = downloadImage();
}
Edit: asyncTask is your background thread and is not UI thread so if you want to do any UI work then you will have to perform that work in UI thread by runOnUiThread() method
runOnUiThread(new Runnable() {
#Override
public void run() {
/* update your UI here for example what you are doing something */;
profile = result2;
}
});

Categories

Resources