You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google Play测试:虚拟Pixel 2上App因ActivityResultLauncher空指针崩溃

Google Play控制台测试仅Pixel 2(Android 12)设备崩溃问题

问题描述

在Google Play控制台发布应用时,仅虚拟Google Pixel 2设备(1080x1920,Android 12,SDK 31,x86_64 en_US)出现崩溃,其余9台测试设备均正常。崩溃为两个相似的空指针异常,均指向ActivityResultLauncher.launch()调用。

错误信息

java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.activity.result.ActivityResultLauncher.launch(java.lang.Object)' on a null object reference

详细堆栈跟踪

FATAL EXCEPTION: Thread-2
Process: xxxxxxxx, PID: 8724
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.activity.result.ActivityResultLauncher.launch(java.lang.Object)' on a null object reference
    at xxxxxxxx.models.StockInputDialog.lambda$init$9$xxxxxxxx-models-StockInputDialog(StockInputDialog.java:417)
    at xxxxxxxx.models.StockInputDialog$$ExternalSyntheticLambda9.onClick(Unknown Source:2)
    at android.view.View.performClick(View.java:7441)
    at android.view.View.performClickInternal(View.java:7418)
    at android.view.View.access$3700(View.java:835)
    at android.view.View$PerformClick.run(View.java:28676)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at androidx.test.espresso.base.Interrogator.loopAndInterrogate(Interrogator.java:10)
    at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:7)
    at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:1)
    at androidx.test.espresso.base.UiControllerImpl.injectMotionEvent(UiControllerImpl.java:5)
    at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:6)
    at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:1)
    at androidx.test.espresso.action.Tap.sendSingleTap(Tap.java:5)
    at androidx.test.espresso.action.Tap.-$$Nest$smsendSingleTap(Unknown Source:0)
    at androidx.test.espresso.action.Tap$1.sendTap(Tap.java:1)
    at androidx.test.espresso.action.GeneralClickAction.perform(GeneralClickAction.java:4)
    at androidx.test.espresso.ViewInteraction$SingleExecutionViewAction.perform(ViewInteraction.java:2)
    at androidx.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:21)
    at androidx.test.espresso.ViewInteraction.-$$Nest$mdoPerform(Unknown Source:0)
    at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:6)
    at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:1)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7839)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

代码实现(迁移至registerForActivityResult的简化结构)

public class StockActivity extends AppCompatActivity implements DialogCloseListener {
    private ActivityResultLauncher<Intent> stockGalleryActivityResultLauncher;
    private ActivityResultLauncher<Intent> stockCameraActivityResultLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        stockCameraActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    // 结果处理逻辑
                });
        stockGalleryActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    // 结果处理逻辑
                });
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_add) {
            mStockInputDialog = new StockInputDialog(this, stockCameraActivityResultLauncher, stockGalleryActivityResultLauncher);
            return true;
        } else {
            return super.onOptionsItemSelected(item);
        }
    }
}

public class StockInputDialog {
    private final Context mContext;
    private AlertDialog mInputDialog;
    private ActivityResultLauncher<Intent> stockCameraActivityResultLauncher;
    private ActivityResultLauncher<Intent> stockGalleryActivityResultLauncher;

    public StockInputDialog(Context context, ActivityResultLauncher<Intent> pStockCameraActivityResultLaunchera, ActivityResultLauncher<Intent> pStockGalleryActivityResultLauncher) {
        mContext = context;
        stockCameraActivityResultLauncher = pStockCameraActivityResultLaunchera;
        stockGalleryActivityResultLauncher = pStockGalleryActivityResultLauncher;
        LayoutInflater li = LayoutInflater.from(this.mContext);
        View mPromptsView = li.inflate(R.layout.text_input_stock, null);
        final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this.mContext);
        alertDialogBuilder.setView(mPromptsView);

        final ImageButton imgButtonCam = mPromptsView.findViewById(R.id.addCam);
        final ImageButton imgButtonGal = mPromptsView.findViewById(R.id.addGal);

        imgButtonCam.setOnClickListener(view -> {
            Uri uri = FileProvider.getUriForFile(mContext, "fr.foo.bar.provider",
                    new Product(Consts.TEMP_NUM).getIMGPathCacheFile(mContext));
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            stockCameraActivityResultLauncher.launch(intent); // 崩溃触发点
        });

        imgButtonGal.setOnClickListener(view -> {
            stockGalleryActivityResultLauncher.launch(new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)); // 崩溃触发点
        });
    }
}

问题分析与修复方案

核心原因

ActivityResultLauncher对象为null,调用launch()时触发空指针异常。大概率是配置变更导致Activity重建:Android 12的Pixel 2设备测试过程中可能发生屏幕旋转等配置变更,此时StockActivity重建,但StockInputDialog引用的是旧Activity的Launcher实例,旧实例已失效并被置为null。

修复方案

方案1:通过Activity弱引用获取最新Launcher

不要直接传递Launcher给Dialog,改为持有Activity的弱引用,调用时从Activity获取最新实例,避免内存泄漏:

public class StockInputDialog {
    private final WeakReference<StockActivity> mActivityRef;
    private AlertDialog mInputDialog;

    public StockInputDialog(StockActivity activity) {
        mActivityRef = new WeakReference<>(activity);
        // 其他初始化逻辑...

        imgButtonCam.setOnClickListener(view -> {
            StockActivity activity = mActivityRef.get();
            if (activity != null) {
                Uri uri = FileProvider.getUriForFile(activity, "fr.foo.bar.provider",
                        new Product(Consts.TEMP_NUM).getIMGPathCacheFile(activity));
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
                activity.getStockCameraActivityResultLauncher().launch(intent);
            }
        });
    }
}

同时在StockActivity中添加Launcher的getter方法:

public ActivityResultLauncher<Intent> getStockCameraActivityResultLauncher() {
    return stockCameraActivityResultLauncher;
}

public ActivityResultLauncher<Intent> getStockGalleryActivityResultLauncher() {
    return stockGalleryActivityResultLauncher;
}

方案2:用ViewModel托管Launcher

利用ViewModel的生命周期独立性,避免配置变更导致的实例失效:

public class StockViewModel extends ViewModel {
    private ActivityResultLauncher<Intent> stockCameraLauncher;
    private ActivityResultLauncher<Intent> stockGalleryLauncher;

    public void initLaunchers(ActivityResultRegistry registry) {
        if (stockCameraLauncher == null) {
            stockCameraLauncher = registry.register("camera_launcher", new ActivityResultContracts.StartActivityForResult(),
                    result -> {
                        // 结果处理逻辑
                    });
        }
        if (stockGalleryLauncher == null) {
            stockGalleryLauncher = registry.register("gallery_launcher", new ActivityResultContracts.StartActivityForResult(),
                    result -> {
                        // 结果处理逻辑
                    });
        }
    }

    public ActivityResultLauncher<Intent> getStockCameraLauncher() {
        return stockCameraLauncher;
    }

    public ActivityResultLauncher<Intent> getStockGalleryLauncher() {
        return stockGalleryLauncher;
    }
}

在StockActivity的onCreate中初始化ViewModel:

private StockViewModel mViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mViewModel = new ViewModelProvider(this).get(StockViewModel.class);
    mViewModel.initLaunchers(getActivityResultRegistry());
}

Dialog中通过ViewModel获取Launcher:

public StockInputDialog(StockActivity activity) {
    StockViewModel viewModel = new ViewModelProvider(activity).get(StockViewModel.class);
    // ...
    imgButtonCam.setOnClickListener(view -> {
        // ...
        viewModel.getStockCameraLauncher().launch(intent);
    });
}

方案3:临时空指针检查(不推荐)

在调用launch()前添加判空,避免崩溃,但无法解决根本问题:

imgButtonCam.setOnClickListener(view -> {
    // ...
    if (stockCameraActivityResultLauncher != null) {
        stockCameraActivityResultLauncher.launch(intent);
    }
});

内容的提问来源于stack exchange,提问作者Chris972

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 15:54:42