【问题标题】:Loading Images from URL Is Producing Unexpected Results从 URL 加载图像会产生意外结果
【发布时间】:2020-10-13 14:56:07
【问题描述】:

我正在将 Wikipedia 中的图像加载到网格视图中。在大多数情况下,这可以正常工作。因为可能会加载多达 200 个或更多的图像,所以我尝试在新线程中运行它。从我的专辑选项卡滚动到正在加载图像的艺术家选项卡时,我看到了明显的延迟。我还看到一些滞后,因为在上下滚动列表时图像仍在加载。此外,当我滚动回列表顶部时,以前由默认图像占据的占位符因为我无法从 Wikipedia 获取图像,现在被其他艺术家的图像占据。

当我滚动回歌曲列表,然后返回艺术家列表时,视图会重置,但进入艺术家选项卡时仍然有很多延迟。

此图像是首次进入“艺术家”选项卡时的屏幕外观。

此图像是滚动到列表底部并返回顶部后的屏幕外观。

如您所见,

这是我调用以从 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(' ','_');
        try {
            String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
            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 = "";
    String html;
    StringBuilder ma = new StringBuilder();
    while ((line = reader.readLine()) != null) {
        ma.append(line);
        Log.i(ContentValues.TAG, "StringBuilder " + ma);
    }
    Log.i(ContentValues.TAG, "Final StringBuilder " + ma);
    return ma;
}

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

这是调用 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("");
            }
        };

        t.start();
        return view;
    }

    public class LoadData extends AsyncTask<String, Void, String> {

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

        @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();
        }
    }
}

我也尝试过使用毕加索使用以下代码:

 bandName = artist.artistName;
    bandName = bandName.replace(' ','_');
    try {
           String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
                    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();
                }

结果与我使用 Android-Universal-Image-Loader 时的结果几乎相同。我已经尝试了几天来解决这个问题,我尝试了几个不同的例子,我在 Stack Overflow 上发现了这些例子,但它们似乎都没有解决我看到的问题。我希望有人能够识别我做错了什么。 提前致谢。

ArtistFragment 转换为 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("")
    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<Artist> {
    val newItems = ArrayList<Artist>()

    withContext(Dispatchers.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(Artist(id, title, albumCount = 0, songCount = 0, artistUrl = url!!))
                }
            }
        }
    }
    return newItems
}

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

我相信我已经解决了我之前遇到的大部分问题我现在归结为以下问题:

Artist.item.map { artist -&gt; - 不确定如何调用,未解决的参考:项目

}.awaitAll().forEach { response -&gt; = forEach 告诉我重载分辨率不明确。所有这些功能都匹配。 public inline fun Iterable.forEach(action: (TypeVariable(T)) → Unit):在 kotlin.collections 中定义的单位 public inline fun Map.forEach(action: (Map.Entry) → Unit):在kotlin中定义的单位.collections

newItems.add(Artist(id, title, url)) - 我知道 Artist Model 的变量需要放在这里,但是当我把它们放在那里时,它们没有得到解决。

我已经重新设计了 ArtistAdapter,但不确定它是否正确。

class ArtistAdapter(private val context: ArtistFragment, private val artistList: List<Artist>?) : RecyclerView.Adapter<ArtistAdapter.ARV>() {

private var dimension: Int = 64

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

var items: MutableList<Artist> = 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 片段

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 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.art/javalib/core-oj.jar)
            at okhttp3.internal.Util.<clinit>(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)

【问题讨论】:

  • 你为什么把 asynctask 调用放在线程运行中?取下螺纹。这没有任何意义。
  • 如果 Artist== null 你应该设置你的默认图片。
  • 如果我删除线程它会更加滞后。我这样做是因为 logcat 告诉 UI 线程正在做很多工作。至于艺术家==null,总有一个艺术家。 UIL 正在使用 .showImageOnLoading(R.drawable.album) 设置默认图像,我不明白你们 cmets。
  • 您听说过 Glide 或 Picasso 图书馆吗?
  • @Nirel 是的,我听说过 Picasso 和 Glide。我正在使用 Picasso 和 Universal Image Loader 来加载图像。这不是我要问的。谢谢

标签: javascript java android


【解决方案1】:

在我看来,它是毕加索的一个已知错误。 尝试手动加载默认图片,以免被缓存的图片替换。

14.10.20 更新: 我认为主要问题是您以相当低效的方式在适配器中加载网络内容。我建议首先形成一个所有 url 的列表,在适配器中只留下图像加载。

还建议您使用 rerofit2 进行网络调用和异步工作,而不是 AsyncTask:rxJava、courutines、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 val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)

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

改造服务:

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

【讨论】:

  • 不,这并没有解决问题。默认图像仍会被替换,并且在进入和滚动“艺术家”选项卡时仍然滞后。
  • VasiliyT 我对 Kotlin 不是很熟悉。我可以将我当前的代码与您提供的代码混合使用吗?
  • 是的,Java 和 Kotlin 可以很好地融合在一起。您还可以通过用 getter 替换属性访问来相对轻松地转换为 java,?带有空检查等等。
  • 您是否可以用Java 提供上述代码。我很难重写它并融入我自己的。
  • 将协程迁移到 java 并不容易。我最好留下一个带有示例代码的存储库链接github.com/VasylT/NetworkingTestApp
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多