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

Android静态蓝牙管理类无法获取View显示SnackBar问题求助

问题描述

我在Android应用里实现了蓝牙连接管理类BluetoothService,为了全局调用把它设成MainActivity的静态变量,结果没法传递或存储Context,进而拿不到显示SnackBar需要的View。我在这个类里用runOnUiThread调用snackbarMsg方法试图显示SnackBar,之前通过构造函数传View只在主页面首次显示时有效,切换Activity后失效还会引发内存泄漏。现在运行出现空指针异常,相关代码和报错信息如下,求可行解决方案。

MainActivity 代码片段

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener{
    //Initialise some static variables needed across the whole program
    private static BluetoothService bluetoothService;
    private static final String TAG = MainActivity.class.getSimpleName();
    protected static ArrayList<LocationData> locations = new ArrayList<>();
    protected static List<String> categories = new ArrayList<>();
    Spinner spinner;
    private static boolean doneFirstRun = false;
    protected static LocationData selectedLocation = new LocationData();
    MainActivity instance = this;

    public MainActivity() {
    }

    /*
    TODO: view in bluetooth service causes memory leak
     */

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        try{
            if (!doneFirstRun){
                //Getting storage
                readFromFile(MainActivity.this);

                //adding example location
                if (locations.size()==0){
                    LocationData spaceRay = new LocationData();
                    spaceRay.setName("SpaceRay");
                    spaceRay.setLatitude(59.40384);
                    spaceRay.setLongitude(17.95228);
                    spaceRay.setInclination(-85);
                    spaceRay.setDirection(200);
                    spaceRay.setAltitude(99990);
                    locations.add(spaceRay);
                }

                //Checking and asking for relevant permissions
                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                    @SuppressLint("InlinedApi") String[] permissions = {Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN};
                    ActivityCompat.requestPermissions(instance, permissions, 1);
                }

                bluetoothService = new BluetoothService();
                //starting bluetooth networking activity on new thread
                startBluetoothService();

                doneFirstRun = true;
            }
        } catch (Exception exception){
            Log.e(TAG, "Failed to do first run through of code: ", exception);
        }
    }

    //...

    protected void startBluetoothService(){
        try {
            Log.i(TAG, "New thread started");
            bluetoothService.run(MainActivity.this);
        } catch (Exception e) {
            Log.e(TAG, "Bluetooth service failed: ", e);
            snackbarMsg("Bluetooth service failed");
        }
    }
}

BluetoothService 代码片段

public class BluetoothService extends AppCompatActivity{
    private static final String TAG = BluetoothService.class.getSimpleName();
    private OutputStream outputStream;
    private InputStream inputStream;
    private BluetoothSocket socket;
    private boolean writing = false;

    public BluetoothService(){
    }//constructor

    //...

    //method to show snackbar message at the bottom of the screen
    public Runnable snackbarMsg (String msg){
        try {
            View view = findViewById(R.id.btn_connect);
            Snackbar snackbar = Snackbar.make(view, msg, BaseTransientBottomBar.LENGTH_SHORT);
            snackbar.show();
        } catch (Exception exception){
            Log.e(TAG, "Could not show snackbar", exception);
        }
        return null;
    }
}

报错信息

java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.ApplicationInfo android.content.Context.getApplicationInfo()' on a null object reference
at android.content.ContextWrapper.getApplicationInfo(ContextWrapper.java:190)
at android.view.ContextThemeWrapper.getTheme(ContextThemeWrapper.java:174)
at android.content.Context.obtainStyledAttributes(Context.java:809)
at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:852)
at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:819)
at androidx.appcompat.app.AppCompatDelegateImpl.findViewById(AppCompatDelegateImpl.java:640)
at androidx.appcompat.app.AppCompatActivity.findViewById(AppCompatActivity.java:261)
at antennalocator.util.BluetoothService.snackbarMsg(BluetoothService.java:204)
at antennalocator.util.BluetoothService.lambda$write$4$antennalocator-util-BluetoothService(BluetoothService.java:159)
at antennalocator.util.BluetoothService$$ExternalSyntheticLambda5.run(Unknown Source:4)
at java.lang.Thread.run(Thread.java:1012)
解决方案

1. 重构BluetoothService,移除AppCompatActivity继承

BluetoothService是业务管理类,不是UI组件,手动实例化的Activity没有经过系统初始化流程,Context为空,这是空指针的核心原因。修改代码:

public class BluetoothService {
    private static final String TAG = BluetoothService.class.getSimpleName();
    private OutputStream outputStream;
    private InputStream inputStream;
    private BluetoothSocket socket;
    private boolean writing = false;

    // 保留原有业务方法,移除UI相关代码
}

2. 用回调模式分离UI与业务逻辑

不让BluetoothService直接处理UI,通过回调把消息通知给当前活跃的UI组件:

  • 在BluetoothService中定义回调接口:
public interface BluetoothCallback {
    void onShowSnackbar(String message);
}

private BluetoothCallback callback;

// 提供设置/清空回调的方法
public void setCallback(BluetoothCallback callback) {
    this.callback = callback;
}
  • 替换原有调用snackbarMsg的代码,改用回调通知:
// 比如在蓝牙连接失败时
if (callback != null) {
    callback.onShowSnackbar("蓝牙连接失败");
}
  • 在MainActivity中实现回调,确保在UI线程显示SnackBar,同时在销毁时清空回调避免内存泄漏:
@Override
protected void onCreate(Bundle savedInstanceState) {
    // ... 原有代码
    bluetoothService = new BluetoothService();
    bluetoothService.setCallback(new BluetoothService.BluetoothCallback() {
        @Override
        public void onShowSnackbar(String message) {
            runOnUiThread(() -> {
                View view = findViewById(R.id.btn_connect);
                if (view != null) {
                    Snackbar.make(view, message, Snackbar.LENGTH_SHORT).show();
                }
            });
        }
    });
    startBluetoothService();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (bluetoothService != null) {
        bluetoothService.setCallback(null);
    }
}

3. 优化全局BluetoothService的管理

静态变量持有Activity引用容易导致内存泄漏,改用单例模式并持有Application Context:

public class BluetoothService {
    private static BluetoothService instance;
    private Context appContext;

    private BluetoothService(Context context) {
        this.appContext = context.getApplicationContext();
    }

    public static synchronized BluetoothService getInstance(Context context) {
        if (instance == null) {
            instance = new BluetoothService(context);
        }
        return instance;
    }

    // ... 其他业务方法
}

在MainActivity中获取实例:

bluetoothService = BluetoothService.getInstance(this);

4. 处理多Activity切换场景

如果需要在多个Activity中显示蓝牙消息,推荐用Jetpack LiveData实现全局消息通知:

  • 创建一个单例的LiveData持有者:
public class BluetoothMessageLiveData {
    private static MutableLiveData<String> instance;

    public static MutableLiveData<String> getInstance() {
        if (instance == null) {
            instance = new MutableLiveData<>();
        }
        return instance;
    }
}
  • 在BluetoothService中发送消息:
BluetoothMessageLiveData.getInstance().postValue("蓝牙消息内容");
  • 在需要接收消息的Activity中观察LiveData:
@Override
protected void onCreate(Bundle savedInstanceState) {
    // ... 原有代码
    BluetoothMessageLiveData.getInstance().observe(this, message -> {
        View view = findViewById(R.id.btn_connect);
        if (view != null) {
            Snackbar.make(view, message, Snackbar.LENGTH_SHORT).show();
        }
    });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 04:25:28