从URL加载图像产生意外结果

huangapple go评论85阅读模式
英文:

Loading Images from URL Is Producing Unexpected Results

问题

I am loading images from Wikipedia into a Grid view. For the most part this is working correctly. Because there could possibly be up to 200 or more images being loaded I am trying to run it in a new thread. I see a definite delay when scrolling from my Album tab to the Artist tab that is loading the images. I am also seeing some lag as images are still getting loaded while scrolling up and down the list. Also when I scroll back to the top of the list, placeholders that were previously occupied by the default image because I am unable to get an image from Wikipedia are now occupied by images from another artist.

When I scroll back to the song list and then back to the artist list, the view is reset but it still has a lot of delay when going into the artist tab.

This image is what the screen looks like when first entering the Artist tab.

从URL加载图像产生意外结果

This image is what the screen looks like after scrolling to the bottom of the list and back to the top.

从URL加载图像产生意外结果

As you can see the <unknown> and AJR have had their default image replaced.

Here is my code that I am calling to load the images from Wikipedia.

// Code here

Here is the code that is calling the Artist Fragment.

// Code here

I have also tried this using Picasso using the following code:

// Code here

The results are pretty much the same as when I used Android-Universal-Image-Loader. I have been trying for several days to fix this, I have tried several different examples that I found on Stack Overflow but none of them seem to resolve the issues I am seeing. I am hoping that someone will be able to identify what I am doing incorrectly. Thanks in advance.

ArtistFragment converted to Kotlin:

// Kotlin code here

I believe I have resolved most of the issues I was previously seeing. I am now down to the following problems:

  1. Artist.item.map { artist -> - Not sure how this should be called, Unresolved reference: item

  2. }.awaitAll().forEach { response -> - forEach is telling me Overload resolution ambiguity. All these functions match.

  3. newItems.add(Artist(id, title, url)) - I know that the variables for the Artist Model need to go here, but when I put them there they are unresolved.

Logcat Snippet:

// Logcat error message here

Please note that I can only provide a general translation of the code and issues you mentioned. If you need specific help with your code or further explanations, feel free to ask.

英文:

I am loading images from Wikipedia into a Grid view. For the most part this is working correctly. Because there could possible be up to 200 or more images being loaded I am try to run it in a new thread. I see a definite delay when scrolling from my Album tab to the Artist tab that is loading the images. I am also see some lag as images are still getting load while scrolling up and down the list. Also when I scroll back to the top of the list place holders that previously occupied by the default image because I am unable to get an image from Wikipedia are now occupied by images from another artist.

When I scroll back to the song list and then back to the artist list the view is reset but it still has a lot of delay when going into the artist tab.

This image is what the screen looks like when first entering the Artist tab.

从URL加载图像产生意外结果

This image is what the screen looks like after scrolling to the bottom of the list and back to the top.

从URL加载图像产生意外结果

As you can see the <unknow. and AJR have had their default image replaced.

Here is my code that I am calling to load the images from Wikipedia.

@Override
public void onBindViewHolder(@NonNull ARV holder, int position) {

    Artist artist = artistList.get(position);
    if(artist!=null) {
        holder.artistName.setText(artist.artistName);
        String bandName = artist.artistName;
        bandName = bandName.replace(&#39; &#39;,&#39;_&#39;);
        try {
            String imageUrl = cutImg(getUrlSource(&quot;https://en.wikipedia.org/w/api.php?action=query&amp;titles=&quot;+bandName+&quot;&amp;prop=pageimages&amp;format=json&amp;pithumbsize=250&quot;));
            URL url = new URL(imageUrl);
            ImageLoader.getInstance().displayImage(imageUrl, holder.artistImage,
                    new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
                            .resetViewBeforeLoading(true).build());
        } catch (IOException e) {
            e.printStackTrace();
        }

        /*ImageLoader.getInstance().displayImage(getCoverArtPath(context,artist.id),holder.artistImage,
                new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
                        .resetViewBeforeLoading(false).build());*/
        }
}

private StringBuilder getUrlSource(String site) throws IOException {
    StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);
    URL localUrl = null;
    localUrl = new URL(site);
    URLConnection conn = localUrl.openConnection();
    BufferedReader reader = new BufferedReader(
            new InputStreamReader(conn.getInputStream()));
    String line = &quot;&quot;;
    String html;
    StringBuilder ma = new StringBuilder();
    while ((line = reader.readLine()) != null) {
        ma.append(line);
        Log.i(ContentValues.TAG, &quot;StringBuilder &quot; + ma);
    }
    Log.i(ContentValues.TAG, &quot;Final StringBuilder &quot; + ma);
    return ma;
}

public static String cutImg(StringBuilder split){
    int start=split.indexOf(&quot;\&quot;source\&quot;:&quot;)+new String(&quot;\&quot;source\&quot;:\&quot;&quot;).length();
    split.delete(0, start);
    split.delete(split.indexOf(&quot;\&quot;&quot;), split.length());
    Log.i(ContentValues.TAG, &quot;StringBuilder &quot; + split);
    return split.toString();
}

Here is the code that is call the Artist Fragment.

public class ArtistFragment extends Fragment {

    int spanCount = 3; // 2 columns
    int spacing = 20; // 20px
    boolean includeEdge = true;

    private RecyclerView recyclerView;
    private ArtistAdapter adapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_artist, container, false);
        recyclerView = view.findViewById(R.id.artistFragment);
        recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));

        Thread t = new Thread()
        {
            public void run()
            {
                // put whatever code you want to run inside the thread here.
                new LoadData().execute(&quot;&quot;);
            }
        };

        t.start();
        return view;
    }

    public class LoadData extends AsyncTask&lt;String, Void, String&gt; {

        @Override
        protected String doInBackground(String... strings) {
            if(getActivity()!=null) {
                adapter=new ArtistAdapter(getActivity(),new ArtistLoader().artistList(getActivity()));
            }
            return &quot;Executed&quot;;
        }

        @Override
        protected void onPostExecute(String s) {
            recyclerView.setAdapter(adapter);
            if(getActivity()!=null) {
                recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
            }
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }
    }
}

I have also tried this using Picasso using the following code:

 bandName = artist.artistName;
    bandName = bandName.replace(&#39; &#39;,&#39;_&#39;);
    try {
           String imageUrl = cutImg(getUrlSource(&quot;https://en.wikipedia.org/w/api.php?action=query&amp;titles=&quot;+bandName+&quot;&amp;prop=pageimages&amp;format=json&amp;pithumbsize=250&quot;));
                    URL url = new URL(imageUrl);
                    Picasso.get().load(imageUrl).placeholder(R.drawable.album)
                            .error(R.drawable.artistdefault).into(holder.artistImage);
                } catch (IOException e) {
                    e.printStackTrace();
                }

The results are pretty much the same as when I used Android-Universal-Image-Loader. I have been try for several days to fix this, I have tried several different examples that I found on Stack overflow but none of them seem to resolve the issues I am seeing. I am hoping that someone will be able to identify what I am doing incorrectly.
Thanks in advance.

ArtistFragmentconverted to Kotlin

class ArtistFragment : Fragment() {
var spanCount = 3 // 2 columns
var spacing = 20 // 20px
var includeEdge = true

var retrofit: Retrofit? = null
var wikiService: WikiService? = null
var adapter: ArtistAdapter? = null

private var recyclerView: RecyclerView? = null

private var viewModelJob = Job()
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)

private var progress_view: ProgressBar? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    val view = inflater.inflate(R.layout.fragment_artist, container, false)
    recyclerView = view.findViewById(R.id.artistFragment)
    recyclerView?.setLayoutManager(GridLayoutManager(activity, 3))
    progress_view = view.findViewById(R.id.progress_view)
    initWikiService()
    initList()
    //LoadData().execute(&quot;&quot;)
    return view
}

override fun onDestroy() {
    super.onDestroy()
    viewModelJob.cancel()
}

private fun initList() {
    recyclerView!!.addItemDecoration(GridSpacingItemDecoration(spanCount, spacing, includeEdge))
    adapter = ArtistAdapter(this)
    adapter?.items = ArrayList()
    adapter?.listener = this
    recyclerView?.adapter = adapter

    viewModelScope.launch {
        progress_view.visibility = View.VISIBLE
        val wikiPages = getWikiPages()
        adapter?.items = wikiPages
        progress_view?.visibility = View.GONE
    }
}

private suspend fun getWikiPages(): ArrayList&lt;Artist&gt; {
    val newItems = ArrayList&lt;Artist&gt;()

    withContext(Dispatchers.IO) {
        ArtistData.artists.map { artist -&gt;

            async { wikiService?.getWikiData(artist) }
        }.awaitAll().forEach { response -&gt;
            val pages = response?.body()?.query?.pages
            pages?.let {
                for (page in pages) {
                    val value = page.value
                    val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
                    val title = value.title ?: &quot;Unknown&quot;
                    val url = value.thumbnail?.source
                    newItems.add(Artist(id, title, albumCount = 0, songCount = 0, artistUrl = url!!))
                }
            }
        }
    }
    return newItems
}

private fun initWikiService() {
    retrofit = Retrofit.Builder()
            .baseUrl(&quot;https://en.wikipedia.org/&quot;)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    wikiService = retrofit?.create(WikiService::class.java)
}

I believe I have resolved most of the issues I was previously seeing I am now down to the following problems:

Artist.item.map { artist -&gt; - Not sure how this should be called, Unresolved reference: item

}.awaitAll().forEach { response -&gt; = forEach is telling me Overload resolution ambiguity. All these functions match.
public inline fun <T> Iterable<TypeVariable(T)>.forEach(action: (TypeVariable(T)) → Unit): Unit defined in kotlin.collections
public inline fun <K, V> Map<out TypeVariable(K), TypeVariable(V)>.forEach(action: (Map.Entry<TypeVariable(K), TypeVariable(V)>) → Unit): Unit defined in kotlin.collections

newItems.add(Artist(id, title, url)) - I know that the variables for the Artist Model need to go here, but when I put them there they are unresolved.

I have reworked the ArtistAdapter not sure if it is correct though.

class ArtistAdapter(private val context: ArtistFragment, private val artistList: List&lt;Artist&gt;?) : RecyclerView.Adapter&lt;ArtistAdapter.ARV&gt;() {

private var dimension: Int = 64

init {
    val density = context.resources.displayMetrics.density
    dimension = (density * 64).toInt()
    hasStableIds()
}

var items: MutableList&lt;Artist&gt; = ArrayList()
    set(value) {
        field = value
        notifyDataSetChanged()
    }

var listener: Listener? = null

interface Listener {
    fun onItemClicked(item: Artist)
    abstract fun ArtistAdapter(context: ArtistFragment): ArtistAdapter
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ARV {
    return ARV(LayoutInflater.from(parent.context).inflate(R.layout.artist_gride_item, parent,
            false))
}

override fun onBindViewHolder(holder: ARV, position: Int) {
    holder.onBind(getItem(position))
}

private fun getItem(position: Int): Artist = items[position]

override fun getItemId(position: Int): Long = items[position].id

override fun getItemCount(): Int {
    return artistList?.size ?: 0
}

inner class ARV(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {

    private val artistNameView: TextView = itemView.findViewById(R.id.artistName)
    private val artistAlbumArtView: SquareCellView = itemView.findViewById(R.id.artistAlbumArt)

    fun onBind(item: Artist) {
        artistNameView.text=item.artistName

        if(item.artistURL!=null) {
            Picasso.get()
                        .load(item.artistURL)
                        .resize(dimension, dimension)
                        .centerCrop()
                        .error(R.drawable.artistdefualt)
                        .into(artistAlbumArtView)
        } else {
            artistAlbumArtView.setImageResource(R.drawable.artistdefualt)
        }
        itemView.setOnClickListener(this)
    }

    override fun onClick(view: View) {
        val artistId = artistList!![bindingAdapterPosition].id
        val fragmentManager = (context as AppCompatActivity).supportFragmentManager
        val transaction = fragmentManager.beginTransaction()
        val fragment: Fragment
        transaction.setCustomAnimations(R.anim.layout_fad_in, R.anim.layout_fad_out,
                R.anim.layout_fad_in, R.anim.layout_fad_out)
        fragment = ArtistDetailsFragment.newInstance(artistId)
        transaction.hide(context.supportFragmentManager
                .findFragmentById(R.id.main_container)!!)
        transaction.add(R.id.main_container, fragment)
        transaction.addToBackStack(null).commit()
    }
}

}

Logcat Snippet

java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or it

    s super classes (declaration of &#39;java.lang.invoke.LambdaMetafactory&#39; appears in /apex/com.android.art/javalib/core-oj.jar)
            at okhttp3.internal.Util.&lt;clinit&gt;(Util.java:87)
            at okhttp3.internal.Util.skipLeadingAsciiWhitespace(Util.java:321)
            at okhttp3.HttpUrl$Builder.parse(HttpUrl.java:1313)
            at okhttp3.HttpUrl.get(HttpUrl.java:917)
            at retrofit2.Retrofit$Builder.baseUrl(Retrofit.java:506)
            at com.rvogl.androidaudioplayer.fragments.ArtistFragment.initWikiService(ArtistFragment.kt:103)
            at com.rvogl.androidaudioplayer.fragments.ArtistFragment.onCreateView(ArtistFragment.kt:43)

答案1

得分: 1

以下是您要翻译的内容:

我认为这是 Picasso 已知的 bug。尝试手动加载默认图像,这样就不会被缓存的图像替换掉。

更新 2020 年 10 月 14 日:
我认为主要问题是您在适配器中以相当低效的方式加载网络内容。我建议首先形成所有 URL 的列表,只在适配器中加载图像。

我还建议您在网络调用方面使用 retrofit2,并在异步工作方面使用一些替代 AsyncTask 的内容:rxJava、coroutines、flow 等等。

我创建了一个示例项目,使用 retrofit2 + coroutines 进行异步加载数据。

在活动中:

private val viewModelScope = CoroutineScope(Dispatchers.Main)

private fun initWikiService() {
    retrofit = Retrofit.Builder()
        .baseUrl("https://en.wikipedia.org/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    wikiService = retrofit?.create(WikiService::class.java)
}

private fun initList() {
    viewModelScope.launch {
        val wikiPages = getWikiPages()
        adapter?.items = wikiPages
    }
}

private suspend fun getWikiPages(): ArrayList<Item> {
    val newItems = ArrayList<Item>()

    withContext(IO) {
        ArtistData.artists.map { artist ->
            async { wikiService?.getWikiData(artist) }
        }.awaitAll().forEach { response ->
            val pages = response?.body()?.query?.pages
            pages?.let {
                for (page in pages) {
                    val value = page.value
                    val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
                    val title = value.title ?: "Unknown"
                    val url = value.thumbnail?.source
                    newItems.add(Item(id, title, url))
                }
            }
        }
    }
    return newItems
}

在 viewHolder 中:

fun onBind(item: Item) {
    if (item.url != null) {
        Picasso.get()
            .load(item.url)
            .resize(dimension, dimension)
            .centerCrop()
            .error(R.drawable.ic_baseline_broken_image_24)
            .into(pictureView)
    } else {
        pictureView.setImageResource(R.drawable.ic_baseline_image_24)
    }
}

在适配器中: 在构造函数中添加 hasStableIds(),并重写 getItemId 方法:

init {
    hasStableIds()
}

override fun getItemId(position: Int): Long = items[position].id

Retrofit 服务:

interface WikiService {
    @GET("/w/api.php?action=query&prop=pageimages&format=json&pithumbsize=250")
    suspend fun getWikiData(@Query("titles") band: String): Response<WikipediaResponse?>
}
英文:

It looks to me as a known bug with Picasso.
Try to load default image manually so it won't be replaced with cached one.

Update 14.10.20:
I think the main problem is that you load network content in adapter in rather ineffective way. I suggest to form a list of all urls at first, leaving only image load in adapter.

Also reccomend you to use rerofit2 for network calls and something for async work instead of AsyncTask: rxJava, courutines, flow etc.

I created a sample project to load data async using retrofit2+coroutines.

In activity:

private val viewModelScope = CoroutineScope(Dispatchers.Main)

private fun initWikiService() {
    retrofit = Retrofit.Builder()
        .baseUrl(&quot;https://en.wikipedia.org/&quot;)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    wikiService = retrofit?.create(WikiService::class.java)
}

private fun initList() {
    viewModelScope.launch {
        val wikiPages = getWikiPages()
        adapter?.items = wikiPages
    }
}
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)

private suspend fun getWikiPages(): ArrayList&lt;Item&gt; {
    val newItems = ArrayList&lt;Item&gt;()

    withContext(IO) {
        ArtistData.artists.map { artist -&gt;
            async { wikiService?.getWikiData(artist) }
        }.awaitAll().forEach { response -&gt;
            val pages = response?.body()?.query?.pages
            pages?.let {
                for (page in pages) {
                    val value = page.value
                    val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
                    val title = value.title ?: &quot;Unknown&quot;
                    val url = value.thumbnail?.source
                    newItems.add(Item(id, title, url))
                }
            }
        }
    }
    return newItems
}

In viewHolder:

    fun onBind(item: Item) {
        if (item.url != null) {
            Picasso.get()
                .load(item.url)
                .resize(dimension, dimension)
                .centerCrop()
                .error(R.drawable.ic_baseline_broken_image_24)
                .into(pictureView)
        } else {
            pictureView.setImageResource(R.drawable.ic_baseline_image_24)
        }
    }

In adapter: add hasStableIds() to constructor and override getItemId method:

init {
    hasStableIds()
}

override fun getItemId(position: Int): Long = items[position].id

Retrofit Service:

interface WikiService {
    @GET(&quot;/w/api.php?action=query&amp;prop=pageimages&amp;format=json&amp;pithumbsize=250&quot;)
    suspend fun getWikiData(@Query(&quot;titles&quot;) band: String): Response&lt;WikipediaResponse?&gt;
}

huangapple
  • 本文由 发表于 2020年10月13日 22:56:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/64337695.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定