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
相关产品推荐
相关产品推荐

