首页 > 解决方案 > 使用 Picasso/Glide 在 androidTest 中加载资产图像失败

问题描述

简短的问题

如何使用 Picasso 或 Glide 在 androidTest 中成功加载资产图像?

长解释

在 UI 测试中,我想调用我的应用程序将图像加载到 ImageView 中,并对其应用某些操作(例如裁剪、居中等)。为了执行图像加载、裁剪等,我使用毕加索。在 UI 测试中,我想验证通过 UI 触发加载后,附加到 ImageView 的位图正是我打算使用 Picasso 的,即我正在连接 Picasso 并正确应用 Picasso DSL。

为此,我启动了一个 androidTest,提供要验证的图像作为src/androidTest/resources/assets. 我将file:///android_asset/$filename图像文件名注入到我的应用程序中,让毕加索使用AssetManager来加载图像。

尽管应用程序使用存储在设备/模拟器中的常规图像,但测试无法加载资产图像——AssetManager 报告 FileNotFoundException:

     Caused by: java.io.FileNotFoundException: australia.jpg
        at android.content.res.AssetManager.nativeOpenAsset(Native Method)
        at android.content.res.AssetManager.open(AssetManager.java:744)
        at android.content.res.AssetManager.open(AssetManager.java:721)
        at com.squareup.picasso.AssetRequestHandler.load(AssetRequestHandler.java:45)
        at com.squareup.picasso.BitmapHunter.hunt(BitmapHunter.java:206)
        at com.squareup.picasso.RequestCreator.get(RequestCreator.java:396)

顺便说一句,使用 Glide 会导致相同的 FileNotFoundException。

我设置了一个小型仪器测试AssetTest.kt来分析和展示问题。请点击链接在 GitHub 上完整查看。

@RunWith(AndroidJUnit4::class)
@LargeTest
class AssetTest {

    private lateinit var activityScenario: ActivityScenario<TasksActivity>
    private val filename = "victoria-priessnitz-KBIui3I44SY-unsplash.jpg"
    // NOTE: the tests show the same behavior regardless of whether the activity is launched or not
    private val enableActivityUsage = true

    @Before
    fun launchActivity() {
        if (enableActivityUsage) {
            activityScenario = ActivityScenario.launch(TasksActivity::class.java)
        }
    }

    @After
    fun shutdownActivity() {
        if (enableActivityUsage) {
            activityScenario.close()
        }
    }

    @Test
    fun loadingAssetImageWithInstrumentationContextSucceeds() {
        val ctx = InstrumentationRegistry.getInstrumentation().context
        val inputStream = ctx.resources.assets.open(filename)
        val bytes = inputStream.readBytes()
        assertEquals(1903567, bytes.size)
    }

    @Test(expected = FileNotFoundException::class)
    fun loadingAssetImageWithInstrumentationTargetContextFails() {
        val targetCtx = InstrumentationRegistry.getInstrumentation().targetContext
        val inputStream = targetCtx.resources.assets.open(filename)
        inputStream.readBytes()
    }

    @Test(expected = FileNotFoundException::class)
    fun loadingAssetImageWithInstrumentationTargetContextsApplicationContextFails() {
        val targetCtx = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
        val inputStream = targetCtx.resources.assets.open(filename)
        inputStream.readBytes()
    }

    // NOTE: when not using the test orchestrator to separate tests, this tests may fail
    // because the execution shows the same behavior as with targetContext
    @Test(expected = NullPointerException::class)
    fun picassoFailsWithNullPointerExceptionWhenUsingContext() {
        val uri = Uri.parse("file:///android_asset/$filename")
        // synchronous image loading cannot be performed in main thread
        val context = InstrumentationRegistry.getInstrumentation().context
        try {
            Picasso.with(context)
                    .load(uri)
                    .get()
        } catch (e: Exception) {
            // sometimes Android Studio does not display callstacks in debugger
            println(e.message)
            throw e
        }
    }

    @Test(expected = FileNotFoundException::class)
    fun picassoFailsWithExceptionInAssetManagerOpenWhenUsingTargetContext() {
        val uri = Uri.parse("file:///android_asset/$filename")
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        try {
            Picasso.with(context)
                    .load(uri)
                    .get()
        } catch (e: Exception) {
            // sometimes Android Studio does not display callstacks in debugger
            println(e.message)
            throw e
        }
    }
}

我观察到以下情况:

  1. 测试loadingAssetImageWithInstrumentationContextSucceeds可以加载资产而loadingAssetImageWithInstrumentationTargetContextFails不能加载资产——无论是否在 ActivityScenario 中运行。我得出结论,资产存储在可从上下文访问的 APK 中,而不是存储在可从 targetContext 访问的 APK 中。不幸的是,我无法查看引用的 APK,因为设备文件资源管理器无法在 /data/app 中打开它们。

  2. 测试picassoFailsWithNullPointerExceptionWhenUsingContext表明 usingInstrumentationRegistry.getInstrumentation().context会让 Picasso 由于空指针异常而失败。我可以在 Picasso builder internals 中看到 Picasso 没有直接使用提供的上下文,而是调用ContextImpl::getApplicationContext-- where mPackageInfo.getApplicationis null

    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }
  1. 测试picassoFailsWithExceptionInAssetManagerOpenWhenUsingTargetContext表明 usingInstrumentationRegistry.getInstrumentation().targetContext不会导致空指针异常,但会导致上面提到的FileNotFoundExceptiona 。AssetManager我得出结论,附加到帽子的 APKtargetContext.applicationContext不包含资产。

  2. 我对启动活动时创建的所有不同上下文感到困惑。

但回到问题上来:

当然还有其他(更昂贵的)方法来测试我的场景,但我认为应该有一种工作方法可以在仪器测试中使用 Picasso 加载资产。

标签: androidtestingassetspicassoandroid-glide

解决方案


推荐阅读