0

I am fairly new to Android Studio, and App development in general. My background is Embedded firmware. To summarize, I am writing an App that will connect to a BLE enabled hardware device. The App will allow some setup on the device, and then the App will continue to run a Service in the background in order to periodically scan for the hardware device.

I have set up the service as follows (only relevant parts included in the code below):

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;
        }
    }

...
}

I create a class LooperThread. I then create the thread myThread in onCreate of the service. I start the service by calling

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

in MainActivity.

In onStartCommand of the service I initialize several parameters, and then I call the local procedure

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

This procedure informs MainActivity that the service is up and running. Main will now enable a fragment that shows an activity spinner, and it starts to communicate with the hardware device via BLE. The way it does this is to call this procedure in the Service with the case 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;
        }
    }

This in turn calls 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);
    }

And here's where I run into the problem, specifically in this line:

   Message m = myThread.ThreadHandler.obtainMessage();

The value of ThreadHandler is null, even though it was not null when the thread was created. So, of course, a Null Pointer Exception is raised and the App crashes! I have done a lot of reading and debugging, but I cannot figure out what is going on. Here is the logcat info:

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 ----------------------------

And here is the Manifest info:

<?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>

I've really been struggling with this one, and I am hoping that a kind soul will be able to suggest a solution. Thanks all!

I tried to add a lot of debugging information to logcat. Also, tons of reading online (mostly in stackOverflow), unfortunately without any results so far.

  • 1
    Does this answer your question? [What is a NullPointerException, and how do I fix it?](https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – Jens Jul 23 '23 at 14:53
  • Hi Jens Thank you for your reply. I have seen that post before. I am confused about the fact that my Handler is non-null when it is created, and then something sets it to null, even though it is a private object. I will look into the Sonar debugging method. Cheers Rob – Rob Garner Jul 23 '23 at 15:48

0 Answers0