Android Studio:调用方法“Message m = myThread.ThreadHandler.obtainMessage();”时出现空指针异常

Android Studio: Null pointer exception when calling method "Message m = myThread.ThreadHandler.obtainMessage();"

提问人:Rob Garner 提问时间:7/23/2023 更新时间:7/23/2023 访问量:38

问:

我对 Android Studio 和一般的应用程序开发相当陌生。我的背景是嵌入式固件。 总而言之,我正在编写一个应用程序,该应用程序将连接到支持 BLE 的硬件设备。该应用程序将允许在设备上进行一些设置,然后应用程序将继续在后台运行服务,以便定期扫描硬件设备。

我按如下方式设置了服务(以下代码中仅包含相关部分):

public class MyBLEService extends Service {
    public MyBLEService() {
    }

    /**
     * Stops scanning after 8 seconds.
     */
    private static final long SCAN_PERIOD = 8000;
    private static boolean isRunning;
    private static boolean BackgroundScan;
    private static int ActivityStatus;
    public static int LastCommand;
    public static int NumBytesToSend;
    private char CommandErrorCode;
    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    private Bundle myBundle = new Bundle();

    private static BluetoothAdapter myBluetoothAdapter;
    private static ScanCallback mScanCallback;

    private static BluetoothGatt mBluetoothGATT;
    private static BluetoothLeScanner mBluetoothLeScanner;
    private static BluetoothDevice mBluetoothDevice;
    private static BluetoothGattCharacteristic DE_Characteristic;
    public static String ServiceID;
    private static final String NOTIFICATION_CHANNEL_ID ="notification_channel_id";
    private static final String NOTIFICATION_Service_CHANNEL_ID = "service_channel";

    private Runnable ScanDelayRunnable;

    private LooperThread myThread;
    private static Handler mHandler;

    private byte[] HashInput = new byte[100];       // Unencrypted data for encryption algorithm
    private byte[] HashOutput = new byte[100];      // Output buffer for encrypted or decrypted data.
    private byte[] PayloadData = new byte[100];     // Raw payload data (if required)
    private final byte[] EmptyArray = new byte[100];     // Raw payload data (if required)

    private static char temp_16;
    private static char CheckSum;

    private static FragmentManager fm;


    private void startInForeground() {
        int icon = R.mipmap.ic_launcher;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            icon = R.mipmap.ic_launcher;
        }

        Intent notificationIntent = new Intent(this, MyBLEService.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(icon)
                .setContentIntent(pendingIntent)
                .setContentTitle("Got-U BLE Service")
                .setContentText("Running");
        Notification notification=builder.build();

        if(Build.VERSION.SDK_INT>=26) {
            NotificationChannel channel = new NotificationChannel(NOTIFICATION_Service_CHANNEL_ID, "Sync Service", NotificationManager.IMPORTANCE_HIGH);
            channel.setDescription("Service Name");
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);

            notification = new Notification.Builder(this,NOTIFICATION_Service_CHANNEL_ID)
                    .setContentTitle("Got-U BLE Service")
                    .setContentText("Running")
                    .setSmallIcon(icon)
                    .setContentIntent(pendingIntent)
                    .build();
        }
        startForeground(2566, notification);
    }

    private static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }
    
    public void BLEServiceSetBluetoothAdapter(BluetoothAdapter btAdapter) {
        myBluetoothAdapter = btAdapter;
        mBluetoothLeScanner = myBluetoothAdapter.getBluetoothLeScanner();
    }

    public void BLEServiceSetFragmentManager(FragmentManager myFM) {
        fm = myFM;
    }

    private void sendDataActivity(String action, String name, String value)
    {
        Intent sendLevel = new Intent();
        sendLevel.setAction(action);
        sendLevel.putExtra(name, value);
        sendBroadcast(sendLevel);
    }


    class LooperThread extends Thread {
        public Handler ThreadHandler;

        public void run() {
            super.run();
            Looper.prepare();

            ThreadHandler = new Handler(Looper.myLooper()) {
                @SuppressLint("MissingPermission")
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                        switch(msg.what){
                            case Constants.ServiceDoNothing:
                                Log.d("BLE Service", "serviceHandler Value in ServiceDoNothing in Handler: " + myThread.ThreadHandler);
                                break;

                            case Constants.ServiceScanForHub:
                                startScanning(false);
                                break;

                            case Constants.ServiceGetCharacteristics:
                                mBluetoothGATT.discoverServices();
                                break;

                            case Constants.ServiceDisconnect:
                                mBluetoothGATT.close();
                                break;

                            case Constants.ServiceEnableNotifications:
                                setCharacteristicNotification(DE_Characteristic, true);
                                break;

                            case Constants.ServiceSendCommand:
                                // The BLE WriteCharacteristic needs a buffer with the exact
                                // size of bytes to send. Since HashOutput has a fixed size, we
                                // need to create another temporary buffer that is the size
                                // of NumReturnBytes, and copy the data in HashOutput to that buffer:
                                byte[] CommandSendBuffer = new byte[NumBytesToSend];
                                System.arraycopy(msg.obj,0,CommandSendBuffer,0,NumBytesToSend);

                                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
                                    mBluetoothGATT.writeCharacteristic(DE_Characteristic, CommandSendBuffer, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
                                }
                                else{
                                    DE_Characteristic.setValue(CommandSendBuffer);
                                    boolean success = mBluetoothGATT.writeCharacteristic(DE_Characteristic);
                                }
                                break;

                            default: break;
                        }
                }
            };
            Looper.loop();
        }
    }


    @Override
    public void onCreate() {
        // Start up the thread running the service. Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block. We also make it
        // background priority so CPU-intensive work doesn't disrupt our UI.
        Log.d("BLE Service", "Creating service thread");

        myThread = new LooperThread();
        myThread.start();

        isRunning = false;
        ActivityStatus = Constants.ServiceDoNothing;
        MainActivity.ServiceIsActive = false;

        mHandler = new Handler();
        mScanCallback = null;
        startInForeground();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //Toast.makeText(this, "service started", Toast.LENGTH_SHORT).show();

        Log.d("BLE Service", "Starting service");
        isRunning = true;

        Log.d("BLE Service", "serviceHandler Value in OnStart: " + myThread.ThreadHandler);
        SetActivityStatus(Constants.ServiceDoNothing);

        Log.d("BLE Service", "serviceHandler Value in OnStart after sendMessage: " + myThread.ThreadHandler);

        MainActivity.ServiceIsActive = true;
        sendDataActivity(Constants.MessageFromBLEService, Constants.BLEServiceState, Constants.BLEServiceReady);

        // If we get killed, after returning from here, restart
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // We don't provide binding, so return null
        return null;
    }

    @Override
    public void onDestroy() {
        MainActivity.ServiceIsActive = false;
        isRunning = false;
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
    }

    private void DelayedMessageHandler(int what, Object object) {
        new Handler().postDelayed(new Runnable() {
            public void run() {
                Message m = myThread.ThreadHandler.obtainMessage();
                m.what = what;
                if(object != null){
                    m.obj = object;
                }
                myThread.ThreadHandler.sendMessage(m);
            }
        }, 300);
    }


    public int GetActivityStatus() {
        return ActivityStatus;
    }

    public void SubmitPassword(String password, int type) {
        // Copy password characters into PayloadData:

        char[] array;

        // Split up password into array of char. since we cannot
        // directly index the string as an array:
        array = password.toCharArray();

        for(int i =0;i<array.length;i++){
            PayloadData[i] = (byte) array[i];
        }

        // Now set/submit the password:
        if(type == Constants.NEW_PASSWORD)
            LastCommand = Constants.DE_CMD_SET_PASSWORD;
        else
            LastCommand = Constants.DE_CMD_SUBMIT_PASSWORD;

        NumBytesToSend = AssembleCommandResponse(Constants.DATA_PACKET_TYPE_COMMAND,
                Constants.ERROR_NO_ERROR,
                (byte) LastCommand,
                (char) array.length,
                (char) 0xFFFF,
                (char) array.length,
                true);

        SetActivityStatus(Constants.ServiceSendCommand);
    }

    public void SetActivityStatus(int status) {
        ActivityStatus = status;
        //Log.d("BLE Service", "serviceHandler Value in SetActivityStatus: " + serviceHandler);
        switch(status){
            case Constants.ServiceDoNothing:
                Log.d("BLE Service", "serviceHandler Value in ServiceDoNothing: " + myThread.ThreadHandler);
                break;

            case Constants.ServiceScanForHub:
                DelayedMessageHandler(Constants.ServiceScanForHub, null);
                break;

            case Constants.ServiceGetCharacteristics:
                DelayedMessageHandler(Constants.ServiceGetCharacteristics, null);
                break;

            case Constants.ServiceEnableNotifications:
                DelayedMessageHandler(Constants.ServiceEnableNotifications, null);
                break;

            case Constants.ServiceSendCommand:
                DelayedMessageHandler(Constants.ServiceSendCommand, HashOutput);
                break;

            default: break;
        }
    }

...
}

我创建了一个类 LooperThread。然后,我在服务的 onCreate 中创建线程 myThread。 我通过调用启动服务

startService(new Intent(this, MyBLEService.class));

在 MainActivity 中。

在服务的 onStartCommand 中,我初始化了几个参数,然后调用本地过程

sendDataActivity(Constants.MessageFromBLEService, Constants.BLEServiceState, Constants.BLEServiceReady);

此过程通知 MainActivity 服务已启动并正在运行。Main 现在将启用一个显示活动微调器的片段,并开始通过 BLE 与硬件设备通信。执行此操作的方式是在 Service 中调用此过程,案例为 Constants.ServiceScanForHub:

    public void SetActivityStatus(int status) {
        ActivityStatus = status;
        //Log.d("BLE Service", "serviceHandler Value in SetActivityStatus: " + serviceHandler);
        switch(status){
            case Constants.ServiceDoNothing:
                Log.d("BLE Service", "serviceHandler Value in ServiceDoNothing: " + myThread.ThreadHandler);
                break;

            case Constants.ServiceScanForHub:
                DelayedMessageHandler(Constants.ServiceScanForHub, null);
                break;

            case Constants.ServiceGetCharacteristics:
                DelayedMessageHandler(Constants.ServiceGetCharacteristics, null);
                break;

            case Constants.ServiceEnableNotifications:
                DelayedMessageHandler(Constants.ServiceEnableNotifications, null);
                break;

            case Constants.ServiceSendCommand:
                DelayedMessageHandler(Constants.ServiceSendCommand, HashOutput);
                break;

            default: break;
        }
    }

这反过来又调用 DelayedMessageHandler(Constants.ServiceScanForHub, null);

    private void DelayedMessageHandler(int what, Object object) {
        new Handler().postDelayed(new Runnable() {
            public void run() {
                Message m = myThread.ThreadHandler.obtainMessage();
                m.what = what;
                if(object != null){
                    m.obj = object;
                }
                myThread.ThreadHandler.sendMessage(m);
            }
        }, 300);
    }

这是我遇到问题的地方,特别是在这一行中:

   Message m = myThread.ThreadHandler.obtainMessage();

ThreadHandler 的值为 null,即使它在创建线程时不为 null。因此,当然,会引发 Null Pointer 异常,并且应用程序崩溃! 我做了很多阅读和调试,但我无法弄清楚发生了什么。以下是 logcat 信息:

2023-07-23 10:40:42.243 30762-30762 AndroidRuntime          com.example.got_u                    E  FATAL EXCEPTION: main
                                                                                                    Process: com.example.got_u, PID: 30762
                                                                                                    java.lang.NullPointerException: Attempt to read from field 'android.os.Handler com.example.got_u.MyBLEService$LooperThread.ThreadHandler' on a null object reference
                                                                                                        at com.example.got_u.MyBLEService$1.run(MyBLEService.java:350)
                                                                                                        at android.os.Handler.handleCallback(Handler.java:938)
                                                                                                        at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                                                        at android.os.Looper.loop(Looper.java:223)
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:7656)
                                                                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                                                                        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
2023-07-23 10:40:42.266 30762-30762 Process                 com.example.got_u                    I  Sending signal. PID: 30762 SIG: 9
---------------------------- PROCESS ENDED (30762) for package com.example.got_u ----------------------------

这是清单信息:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Request legacy Bluetooth permissions on older devices. -->
    <uses-permission
        android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" /> <!-- Before Android 12 (but still needed location, even if not requested) -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- From Android 12 -->
    <uses-permission
        android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />

    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.GotU"
        tools:targetApi="31">
        <service
            android:name=".MyBLEService"
            android:foregroundServiceType="connectedDevice"
            android:process=":connectedDevice_process"
            android:enabled="true"
            android:exported="false">
        </service>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

我真的一直在为这个问题而苦苦挣扎,我希望一个善良的灵魂能够提出一个解决方案。 谢谢大家!

我尝试向 logcat 添加大量调试信息。此外,在线阅读大量内容(主要是在 stackOverflow 中),不幸的是到目前为止没有任何结果。

android-studio nullpointerexception

评论

1赞 Jens 7/23/2023
这回答了你的问题吗?什么是 NullPointerException,如何修复它?
0赞 Rob Garner 7/23/2023
嗨,Jens,感谢您的回复。我以前看过那个帖子。我对我的 Handler 在创建时是非 null 这一事实感到困惑,然后某些东西将其设置为 null,即使它是一个私有对象。我将研究 Sonar 调试方法。干杯罗伯

答: 暂无答案