I used SimpleDraweeView in adapter, but my app runs slowly, and I saw the log below:
I/Choreographer(1378): Skipped 55 frames! The application may be doing too much work on its main thread.
so I comment the code mSimpleDraw.setImageUri, the app runs fluently. I think the SimpleDraw blocks the main thread when fetching image from network.
Are you using OkHttp networking stack?
There is an issue with OkHttp networking stack that it cancels requests on the calling thread (which is going to be the main thread). We have a fix ready for that.
Other than that, SimpleDraweeView should not be doing anything heavy on the main thread. If you give us more context on how you use Fresco, we may be able to isolate the issue more easily.
@plamenko I just used in a CircleView Adapter and load image, very simple like the tutorial.
in Application: Fresco.initialize(mContext);
in adapter: mImageView.setImageURI(Uri.parse(data.getImage()));
that's it.
What's CircleView Adapter?
The application may be doing too much work on its main thread.
Hitting the same problem here. Pretty basic usage of SimpleDraweeView in a GridView through an adapter:
// MainActivity.java
Fresco.initialize(this);
GridView gv = findViewById(R.id.grid_view);
ImageAdapter ia = new ImageAdapter(this);
gv.setAdapter(ia);
// ImageAdapter.java
public class ImageAdapter extends BaseAdapter {
...
public View getView(int position, View convertView, final ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.grid_item, null);
convertView.setTag(convertView.findViewById(R.id.grid_item_image_view));
}
SimpleDraweeView view = (SimpleDraweeView) convertView.getTag();
// with the following line: major UI slowdown, "the application may be doing too much work on its main thread."
// without the following line: performant as expected
view.setImageURI(Uri.parse("content://media/external/images/media/172699");
return view;
}
...
}
<!-- grid_item.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/grid_item_image_view"
android:layout_width="150dp"
android:layout_height="150dp"
/>
</LinearLayout>
It seems that any parallel calls to setImageURI result in the slowdown.
@plamenko My code just the same as above, in an adapter
I'll investigate
Replacing
view.setImageURI(Uri.parse("content://...");
with
ImageRequest imageRequest =
ImageRequestBuilder.newBuilderWithSource(Uri.parse("content://...")
.setResizeOptions(
new ResizeOptions(150, 150))
.build();
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setImageRequest(imageRequest)
.setOldController(view.getController())
.setAutoPlayAnimations(true)
.build();
view.setController(draweeController);
in my code above fixes the performance problem. If I comment out the setResizeOptions line the performance problem comes back. Perhaps something to do with rescaling on the main thread?
FWIW I can also hack the Fresco Sample app to Skipped 129 frames! by taking the opposite approach to my last comment:
Replace these lines with
Uri uri = getRandomLocalContentURI();
view.setImageURI(uri);
I've found it _only_ hits performance problems dealing with local content:// URIs. http:// do just fine.
That's really helpful information, thanks!
@fuwaneko , yeah OkHttp cancellation is a known issue and we have a fix for that. However, local content uri is something we yet need to figure.
One other potential/related clue. Even when using my workaround above (which fixed the performance problem), I still see one error line for every content://... image that is loaded:
04-11 22:00:25.677 29554-29592/com.example.app E/JHEAD﹕ can't open '/external/images/media/172693'
04-11 22:00:25.677 29554-29592/com.example.app E/JHEAD﹕ can't open '/external/images/media/172692'
04-11 22:00:25.677 29554-29592/com.example.app E/JHEAD﹕ can't open '/external/images/media/172691'
The image still successfully loads but an error is generated for each.
The fix from this Picasso thread is to include file:// in front, presumably something that Fresco needs to do internally or an upstream lib needs to be doing.
I deleted the two comments related to OkHttp cancellation as they are not related to this issue and has already been fixed.
@mikeknoop , I missed part of your commend earlier, where you said "If I comment out the setResizeOptions line the performance problem comes back."
Can you please check what are the dimensions of those local images? If the image is much bigger than the view than there is going to be a noticeable overhead unless we resize the image first. Resize has to be explicitly specified as calling setImageURI won't do that.
@plamenko my local images are indeed bigger than the view. They are camera images so likely ~3000x3000px getting scaled to ~150dpi views.
Can Fresco handle this use case?
@mikeknoop, yes, please see http://frescolib.org/docs/resizing-rotating.html#_
Thanks for the info. I imagine Camera images are the 80% use case for content:// URIs, maybe it'd make sense to do resizing in setImageURI.
I feel like I'm really hacking things to get Fresco to work:
ImageRequestBuilder.newBuilderWithSource(mCameraImages.getUris().get(position))
// I tried this option too
//.setLocalThumbnailPreviewsEnabled(false)
.setResizeOptions(new ResizeOptions(1, 1))
.build();
Resizing to 1x1 seems to pick some minimum size that is suitable. I should add that I haven't been able to beat Picasso w.r.t. loading performance for content:// images yet (and Picasso is pretty slow itself which is why I was checking out Fresco).
You should definitely specify resizing, butResizeOptions(1, 1) doesn't seem right, why don't you pass the view's dimensions?
Btw, if you do .setLocalThumbnailPreviewsEnabled(true) with .setResizeOptions(new ResizeOptions(1, 1)), and also do setControllerListener with the following listener, what do you get in the adb logcat?
ControllerListener listener = new BaseControllerListener<ImageInfo>() {
@Override
public void onIntermediateImageSet(String id, ImageInfo imageInfo) {
android.util.Log.w(TAG, "intermediate: width " + imageInfo.getWidth() + ", " + "height " + imageInfo.getHeight());
}
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable a) {
android.util.Log.w(TAG, "final: width " + imageInfo.getWidth() + ", " + "height " + imageInfo.getHeight());
}
};
Same issue, after using SimpleDraweeView:
The application may be doing too much work on its main thread;
In my case I have init the Fresco in Application, in the adapter use setImageUrl, then the app is closing to ANR with few response;
The issue can be fixed setting ResizeOptions to SimpleDraweeView mentioned by @mikeknoop
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(data.image))
.setResizeOptions(new ResizeOptions(width, height))
.build();
holder.pic.setController(Fresco.newDraweeControllerBuilder()
.setOldController(holder.pic.getController())
.setImageRequest(request)
.build());
I just get the width and height when setImageUri()
public void setImageURI(Uri uri, @Nullable Object callerContext) {
if (!hasHierarchy()) {
inflateHierarchy(getContext());
}
DraweeController controller;
int width = getLayoutWidth() , height = getLayoutHeight();
if (this.mSimpleDraweeControllerBuilder instanceof PipelineDraweeControllerBuilder
&& width>0 && height >0) {
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height))
.setAutoRotateEnabled(true)
.build();
PipelineDraweeControllerBuilder builder =
(PipelineDraweeControllerBuilder) mSimpleDraweeControllerBuilder;
controller = builder
.setAutoPlayAnimations(true)
.setCallerContext(callerContext)
.setOldController(this.getController())
.setImageRequest(request)
.build();
} else
controller = this.mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(this.getController())
.build();
this.setController(controller);
/**
* {@inheritDoc}
*
/**
* {@inheritDoc}
* <p/>
* Height is defined by target {@link android.view.View view} parameters, configuration
* parameters or device display dimensions.<br />
* Size computing algorithm (go by steps until get non-zero value):<br />
* 1) Get the actual drawn <b>getHeight()</b> of the View<br />
* 2) Get <b>layout_height</b>
*/
public int getLayoutHeight() {
final ViewGroup.LayoutParams params = getLayoutParams();
int height = 0;
if (params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
height = getHeight(); // Get actual image height
}
if (height <= 0 && params != null) height = params.height; // Get layout height parameter
if (height <= 0) {
height = getImageViewFieldValue(this, "mMaxHeight"); // Check maxHeight parameter
}
return height;
}
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = (Integer) field.get(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (Exception e) {
}
return value;
}
Setting ResizeOptions is indeed the correct solution. The reason frames were skipped is that Android was struggling to scale such a large image in a small view.
At most we could make this automatic some of the time - when the view is specified with an explicit size - but that won't work for match_parent type layouts.
Hello,
I got entangled in a similar problem where i was using SimpleDraweeView as a GridView row, to parse some http uri's. All images were > 5mb and were being loaded in memory without getting resized automatically (which was causing Out of Memory Error).
@mikeknoop 's solution works but writing such code in getView() seems an inefficient approach.
So, i created my version of SimpleDraweeView, following are the steps i used:
1) Copy/Paste SimpleDraweeView in your package and rename it to something else (I renamed it to TweakedDraweeView)
2) Change the following method as follows:
public void setImageURI(Uri uri, Object callerContext)
{
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri).setResizeOptions(new ResizeOptions(getLayoutParams().width, getLayoutParams().height)).build();
DraweeController draweeController = Fresco.newDraweeControllerBuilder().setImageRequest(imageRequest).setOldController(getController()).setAutoPlayAnimations(true).build();
setController(draweeController);
// exactly same as mikeknoop's
}
3) Step 2 alone won't work because it would throw an exception saying that the SimpleDraweeView has not been initialized, although you have renamed it, but Fresco still seems to look for SimpleDraweeView.
The solution for this problem is to add an extra line where you initialized Fresco(probably in Application class)
TweakedDraweeView.initialize(new PipelineDraweeControllerBuilderSupplier(this)); //since i named my SimpleDraweeView version to TweakedDraweeView
that's all!
Hi ! I had the same problem and what fixed it was to
setLocalThumbnailPreview(FALSE).
Regarding this issue, can someone from fresco answer me these questions:
I also thave the same issue, my app has images and uses fragments and adapters but it is too slow.
Then shows "The application may be doing too much work on its main thread."
This is how my adpater looks like;
`public class UniversityAdapter extends ArrayAdapter
public UniversityAdapter(@NonNull Context context, ArrayList<University> wordArrayList) {
super(context, 0, wordArrayList);
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
View listViewItem = convertView;
if (listViewItem == null) {
listViewItem = LayoutInflater.from(getContext()).inflate(R.layout.university_list_layout, parent, false);
}
University currentUniv = getItem(position);
TextView nameTextView = (TextView) listViewItem.findViewById(R.id.univ_name);
nameTextView.setText(currentUniv.getName());
TextView descTextView = (TextView) listViewItem.findViewById(R.id.unive_description);
descTextView.setText(currentUniv.getDescription());
TextView websiteTextView = (TextView) listViewItem.findViewById(R.id.univ_website);
websiteTextView.setText(currentUniv.getWebsite());
ImageView imageView = (ImageView) listViewItem.findViewById(R.id.univ_image);
imageView.setImageResource(currentUniv.getImageResourceId());
return listViewItem;
}
}`
An the fragment looks like this;
`public class UniversityFragment extends android.support.v4.app.Fragment {
public UniversityFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.grid_view_list, container, false);
ArrayList<University> universities = new ArrayList<University>();
universities.add(new University( "Ndejje University","Hotel Africana is Uganda\'s Premier luxury Hotel. The hotel has over 14 fully furnished well air-conditioned",
"www.africana.com", R.drawable.hotelafricana));
universities.add(new University( "Makerere University","Hotel Africana is Uganda\'s Premier luxury Hotel. The hotel has over 14 fully furnished well air-conditioned",
"www.africana.com", R.drawable.fadingwest));
universities.add(new University( "IUEA","Hotel Africana is Uganda\'s Premier luxury Hotel. The hotel has over 14 fully furnished well air-conditioned",
"www.africana.com", R.drawable.hotelafricana));
universities.add(new University( "Uganda Christian Univesity","Hotel Africana is Uganda\'s Premier luxury Hotel. The hotel has over 14 fully furnished well air-conditioned",
"www.africana.com", R.drawable.fadingwest));
universities.add(new University( "Kyambogo University","Hotel Africana is Uganda\'s Premier luxury Hotel. The hotel has over 14 fully furnished well air-conditioned",
"www.africana.com", R.drawable.hotelafricana));
UniversityAdapter universityAdapter = new UniversityAdapter(getActivity(), universities);
GridView gridView = (GridView) rootView.findViewById(R.id.list);
gridView.setAdapter(universityAdapter);
return rootView;
}
}`
Please what could be wrong?
need help
<< The application may be doing too much work on its main thread >>
And here is my code
ViewModel->
class YoutubeViewModel(apiService: ApiService):ViewModel() {
val youtubeLiveData=MutableLiveData
private val youtubeRepository=YoutubeRepository(apiService)
init {
getVideos()
}
private fun getVideos() {
viewModelScope.launch {
youtubeLiveData.postValue(Resource.loading(null))
youtubeRepository.getYoutubeData()
.catch {e->
youtubeLiveData.postValue(Resource.error(e.message?:"Error",null))
}.collect {
youtubeLiveData.postValue(Resource.success(it))
}
}
}
@JvmName("getYoutubeLiveData1")
fun getYoutubeLiveData():MutableLiveData
return youtubeLiveData
}
}
And in MainFragment
youtubeViewModel=ViewModelProviders.of(this,ViewModelFactory(ApiClient.apiService))[YoutubeViewModel::class.java]
youtubeViewModel.getYoutubeLiveData().observe(viewLifecycleOwner,{
when(it.status){
Status.ERROR->{
binding.progress.visibility=View.INVISIBLE
Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
}
Status.LOADING->{
binding.progress.visibility=View.VISIBLE
}
Status.SUCCESS->{
binding.progress.visibility=View.INVISIBLE
videoAdapter.submitList(it.data?.items)
Log.d(TAG, "onCreateView: ${it.data?.items}")
}
}
})
Most helpful comment
Replacing
with
in my code above fixes the performance problem. If I comment out the
setResizeOptionsline the performance problem comes back. Perhaps something to do with rescaling on the main thread?