Android: Handler's messages delayed at end of worker thread

Viewed 1787

I'm using a thread, inside a service, which do some heavy processing and I want to update the GUI (activity) during this processing. To do this I'm sending messages from thread to a handler and I update the GUI from the handler. But the problem is that the messages are received by the handler only when the worker thread is terminated, as if the message queue was blocked. The reason I used a service is because the process should continue even if the application is not displayed.

The application goal is to test a NFC chip (ISO15693) by sending a list of predefined commands. So sending commands is done by thread, and for each command the result is sent to the handler.

Here is my code :

Application

public class ISO15693Application extends Application {
...
    //Handler receiving messages from Worker thread
    final RunTestHandler runTestHandler = new RunTestHandler(ISO15693Application.this);    
    static class RunTestHandler extends Handler {
        //Avoid leak with handler        
        //See http://stackoverflow.com/questions/11407943/this-handler-class-should-be-static-or-leaks-might-occur-incominghandler
        private final WeakReference<ISO15693Application> mApplication; 

        RunTestHandler(ISO15693Application isoApp) {
            mApplication = new WeakReference<ISO15693Application>(isoApp);
        }

        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);

            ISO15693Application isoApp = mApplication.get();

            switch(msg.what){
            case MSG_TEST_RESULT:
                NfcVResponse r = (NfcVResponse) msg.obj;
                if(r != null){
                    Log.i(TAG, "handleMessage Thread id : " + Thread.currentThread().getId());
                    isoApp.onTestResult((NfcVResponse) msg.obj, msg.arg1);
                }                    
                break;

            case MSG_TEST_STARTED:
                isoApp.onTestStarted(msg.arg1);
                break;
            case MSG_TEST_TERMINATED:
                isoApp.onTestTerminated();
                break;
            case MSG_ABORTED:
                isoApp.onTestAborted();
                break;

            }
        }
    }


    public void onTestResult(NfcVResponse response, int currentCommand) {        
        Log.i(TAG, "onTestResult. Command: " + response.getCommand().toString() 
                + " Status: " + response.getStatus());

        if(testResultCallback != null){
            testResultCallback.onTestResult(response);
        }
    }

    //Called when user click on "Run" button
    public synchronized void runTest(HashMap<Integer, List<Byte>> testMap){

        this.testMap = testMap;        

        //Start last test result activity
        Intent intent = new Intent(getApplicationContext(), TestResultsActivity.class);
        intent.setAction(ISO15693Application.INTENT_ACTION_TEST_STARTED);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);

        //Start service responsible to run the test.
        runTestHandler.postDelayed(new Runnable() {            
            @Override
            public void run() {
                startTestService();                
            }
        }, 500);

    }

    /*
     * This function will be called by ISO15693Service.Runner to run the test in another thread. 
     * Messages are sent to runTestHandler to indicate start, progress and end .  
     */
    public synchronized void _runTest() {

        boolean tagLost = false;
        commandList = buildTest(testMap);

        int total = commandList.size();
        Message startMsg = new Message();
        startMsg.what = MSG_TEST_STARTED;
        startMsg.arg1 = total;
        runTestHandler.sendMessage(startMsg);

        Log.d(TAG, "Start test Thread id: " + Thread.currentThread().getId());

        for(int index = 0;index < total; index++){
            NfcVCommand cmd = commandList.get(index);
            NfcVResponse response = NfcVHelper.sendCommand(getNfcV(), cmd);

            switch(response.getStatus()){
            case NfcHelper.NFC_STATUS_OK:
                Log.i(TAG, "Command sent successfully");
                break;
            case NfcHelper.NFC_STATUS_NO_TAG:
                //Tag has been lost, try to reconnect
                Log.i(TAG, "Tag has been lost, reconnect");
                ...
                break;
            case NfcHelper.NFC_STATUS_ERROR:                
            default:
                Log.i(TAG, "Error when sent command " + response.getResponseString());
                break;
            }

            Message msg = new Message();        
            msg.what = MSG_TEST_RESULT;
            msg.arg1 = index;
            msg.arg2 = total;
            msg.obj = response;
            //Update UI with last command result            
            runTestHandler.sendMessage(msg);

            //Even if waiting a short moment to let the message queue processing
            //messages are handled only when the worker thread ends.
            //The only difference is in the log message : 
            //I/Choreographer(26709): Skipped 34 frames!  The application may be doing too much work on its main thread.
            //The number of Skipped frams is bigger according to time waited. 
            /*try {
                Thread.sleep(100);
            } catch (InterruptedException e1) {                
            }*/

            //Check tag lost and user cancellation
            ...
        }

        //Add results to db
        ...        
        runTestHandler.sendEmptyMessage(MSG_TEST_TERMINATED);        
    }
}

Service

public class ISO15693Service extends Service {

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate");
        //Create updater thread
        this.testRunner = null;
        //get application to access preferences
        isoApp =  (ISO15693Application) getApplication();

        super.onCreate();
    }

    @Override
    public void onDestroy() {

        super.onDestroy();
        Log.d(TAG, "onDestroy");

        if(this.testRunner != null){        
            this.testRunner.interrupt();
            this.testRunner = null;
        }
        this.runFlag = false;
        this.isoApp.setTestRunning(false);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {        
        Log.d(TAG, "onStartCommand");
        //not already running ? 
        if(!this.runFlag){
            try{
                this.testRunner = new Runner();
                this.runFlag = true;
                this.isoApp.setTestRunning(true);    
                this.testRunner.start();                            
            } catch(IllegalThreadStateException e){                
            }
        }        
        return super.onStartCommand(intent, flags, startId);
    }


    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

    private class Runner extends Thread{

        Runner(){
            super("Runner class");
        }

        @Override
        public void run() {
            super.run();
            ISO15693Service is = ISO15693Service.this;
            ISO15693Application isoApp = (ISO15693Application) is.getApplication();

            Log.d(TAG, "Runner.run() Thread id: " + Thread.currentThread().getId());

            isoApp._runTest();
            is.runFlag = false;
            isoApp.setTestRunning(false);            
        }
    }

}

TestResultActivity (Activity showing results)

public class TestResultsActivity extends ISO15693BaseActivity
implements ISO15693Application.TestResultCallback{

    ...

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

        mainLayout = (LinearLayout) findViewById(R.id.resultLayout);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);
        ...
    }    

    @Override
    public void onResume() {    
        super.onResume();
        isoApp.setTestResultCallback(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        //Disable callback if activity is not at foreground
        isoApp.setTestResultCallback(null);
    }

    //Add command result to the main layout 
    @Override
    public void onTestResult(final NfcVResponse response) {
        if(mainLayout != null){
            LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            TestResultView rv = new TestResultView(TestResultsActivity.this, response);
            mainLayout.addView(rv, params);                    
        }
        progressBar.setProgress(progress++);

    }

    @Override
    public void onTestStarted() {
        Log.i(TAG, "onTestStarted");

        Log.d(TAG, "GUI Thread id: " + Thread.currentThread().getId());
        mainLayout.removeAllViews();
        progress = 0;
        progressBar.setVisibility(View.VISIBLE);
        progressBar.setMax(isoApp.getTestInfo().nbCommands);        
    }

    @Override
    public void onTestTerminated() {
        Log.i(TAG, "onTestTerminated");        
        progressBar.setVisibility(View.GONE);                

    }
}

Here is the log, we can see that "handleMessage" and "onTestResult" are called only at the end, after the last "sendCommand" call. But they should be processed directly, or maybe with a small delay, but not delayed as it is. Note that the moment where a message is sent to the handler corresponds to the lines "Command sent successfully" or "Error when sent command ..." in the log.

There is also the message "Skipped 34 frames! The application may be doing too much work on its main thread." I think the problem is here and this message indicates that the GUI has been frozen during 34 frames. But I don't understand why because all the "heavy processing" is done in another thread (id 69595) than GUI thread. I also tried to wait (100-1000ms) between each command processing, but this changes nothing, except that more "Frames" are skipped.

12-16 10:43:19.600: I/ISO15693Application(26709): Activity TestResultsActivity created
12-16 10:43:19.615: D/TestResultsActivity(26709): GUI Thread id: 1
12-16 10:43:20.145: D/ISO15693Service(26709): onCreate
12-16 10:43:20.145: D/ISO15693Service(26709): onStartCommand
12-16 10:43:20.145: D/ISO15693Application(26709): Build Test Thread id: 69595
12-16 10:43:20.150: I/ISO15693Application(26709): Test started: 8 commands
12-16 10:43:20.150: I/TestResultsActivity(26709): onTestStarted
12-16 10:43:20.150: D/TestResultsActivity(26709): GUI Thread id: 1
12-16 10:43:20.150: D/ISO15693Application(26709): Start test Thread id: 69595
12-16 10:43:20.150: D/NfcVHelper(26709): SendCommand Thread id: 69595
12-16 10:43:20.150: D/NfcVHelper(26709): Send command : 00 20 10 
12-16 10:43:20.185: D/NfcVHelper(26709): Response : 00 5a a5 5a a5 
12-16 10:43:20.185: I/ISO15693Application(26709): Command sent successfully
12-16 10:43:20.185: D/NfcVHelper(26709): SendCommand Thread id: 69595
12-16 10:43:20.185: D/NfcVHelper(26709): Send command : 00 21 10 5a a5 5a a5 
12-16 10:43:20.245: D/NfcVHelper(26709): Response : 00 
12-16 10:43:20.245: I/ISO15693Application(26709): Command sent successfully
12-16 10:43:20.245: D/NfcVHelper(26709): SendCommand Thread id: 69595
12-16 10:43:20.245: D/NfcVHelper(26709): Send command : 00 23 01 02 
12-16 10:43:20.290: D/NfcVHelper(26709): Response : 00 00 00 00 00 00 00 00 00 00 00 00 00 
12-16 10:43:20.290: I/ISO15693Application(26709): Command sent successfully
12-16 10:43:20.290: D/NfcVHelper(26709): SendCommand Thread id: 69595
12-16 10:43:20.290: D/NfcVHelper(26709): Send command : 00 27 af 
12-16 10:43:20.330: D/NfcVHelper(26709): Response : 00 
12-16 10:43:20.330: I/ISO15693Application(26709): Command sent successfully
12-16 10:43:20.330: D/NfcVHelper(26709): SendCommand Thread id: 69595
12-16 10:43:20.330: D/NfcVHelper(26709): Send command : 00 29 d5 
12-16 10:43:20.375: D/NfcVHelper(26709): Response : 00 
12-16 10:43:20.375: I/ISO15693Application(26709): Command sent successfully
12-16 10:43:20.375: D/NfcVHelper(26709): SendCommand Thread id: 69595
12-16 10:43:20.375: D/NfcVHelper(26709): Send command : 00 2b 
12-16 10:43:20.410: D/NfcVHelper(26709): Response : 00 xx xx xx xx xx xx xx xx xx xx xx xx xx xx 
12-16 10:43:20.410: I/ISO15693Application(26709): Command sent successfully
12-16 10:43:20.410: D/NfcVHelper(26709): SendCommand Thread id: 69595
12-16 10:43:20.410: D/NfcVHelper(26709): Send command : 00 2c 00 00 
12-16 10:43:20.450: D/NfcVHelper(26709): Response : 00 01 
12-16 10:43:20.450: I/ISO15693Application(26709): Command sent successfully
12-16 10:43:20.450: D/NfcVHelper(26709): SendCommand Thread id: 69595
12-16 10:43:20.450: D/NfcVHelper(26709): Send command : 00 a5 16 
12-16 10:43:20.505: W/System.err(26709): android.nfc.TagLostException: Tag was lost.
12-16 10:43:20.505: W/System.err(26709):    at android.nfc.TransceiveResult.getResponseOrThrow(TransceiveResult.java:48)
12-16 10:43:20.505: W/System.err(26709):    at android.nfc.tech.BasicTagTechnology.transceive(BasicTagTechnology.java:151)
12-16 10:43:20.505: W/System.err(26709):    at android.nfc.tech.NfcV.transceive(NfcV.java:115)
12-16 10:43:20.505: W/System.err(26709):    at em.marin.nfc.NfcVHelper.sendCommand(NfcVHelper.java:283)
12-16 10:43:20.505: W/System.err(26709):    at em.marin.iso15693test.ISO15693Application._runTest(ISO15693Application.java:447)
12-16 10:43:20.505: W/System.err(26709):    at em.marin.iso15693test.ISO15693Service$Runner.run(ISO15693Service.java:88)
12-16 10:43:20.505: I/ISO15693Application(26709): Error when sent command IO Exception occured during transmission of the command
12-16 10:43:20.730: I/Choreographer(26709): Skipped 34 frames!  The application may be doing too much work on its main thread.
12-16 10:43:20.795: I/ISO15693Application(26709): handleMessage Thread id : 1
12-16 10:43:20.795: I/ISO15693Application(26709): onTestResult. Command: Read Single Block Status: 0
12-16 10:43:20.820: I/ISO15693Application(26709): handleMessage Thread id : 1
12-16 10:43:20.820: I/ISO15693Application(26709): onTestResult. Command: Write Single Block Status: 0
12-16 10:43:20.830: I/ISO15693Application(26709): handleMessage Thread id : 1
12-16 10:43:20.830: I/ISO15693Application(26709): onTestResult. Command: Read Multiple Blocks Status: 0
12-16 10:43:20.845: I/ISO15693Application(26709): handleMessage Thread id : 1
12-16 10:43:20.845: I/ISO15693Application(26709): onTestResult. Command: Write AFI Status: 0
12-16 10:43:20.855: I/ISO15693Application(26709): handleMessage Thread id : 1
12-16 10:43:20.855: I/ISO15693Application(26709): onTestResult. Command: Write DSFI Status: 0
12-16 10:43:20.865: I/ISO15693Application(26709): handleMessage Thread id : 1
12-16 10:43:20.865: I/ISO15693Application(26709): onTestResult. Command: Get System Information Status: 0
12-16 10:43:20.875: I/ISO15693Application(26709): handleMessage Thread id : 1
12-16 10:43:20.875: I/ISO15693Application(26709): onTestResult. Command: Get Multiple Block Security Status Status: 0
12-16 10:43:20.885: I/ISO15693Application(26709): handleMessage Thread id : 1
12-16 10:43:20.885: I/ISO15693Application(26709): onTestResult. Command: Read Sig Status: 1
12-16 10:43:20.890: I/ISO15693Application(26709): Test has been terminated successfully
12-16 10:43:20.890: I/TestResultsActivity(26709): onTestTerminated
12-16 10:43:20.955: D/ISO15693Service(26709): onDestroy

I hope my explanations are clear. Architecture of the application can seem strange, but I tried to separate maximum the GUI and processing. Of course any suggestions for improvements or better practice are welcome.

I searched a long time on this forum and the others to find a similar issue but I didn't find so I apologize in advance if this question has already been asked. Sorry also for my english, that's not my native language.

1 Answers
Related