Saturday, April 14, 2018

Simulate the android background process killed by memory shortage

It is useful to test the use case when Android process gets killed by OS due to device memory shortage.

1. Optionally, open Android device monitor to check the status of the process
2. Put the application in background by press home button
3. run the below adb command to kill the process based on the package name

adb shell am kill com.company.myassistantproject
alternatively, you can also kill the process from Android Device Monitor by selecting tthe process and then click "stop" button.
4. optionally, verify the process is killed from Android Device Monitor.
5. Restart the app by clicking the app icon in home button, or bring up the recent app screens and then select the application.

Not if one process's activity A starts another process's activity B. And then process A gets killed by OS due to memory shortage. If Activity B sends result back to Activity A using setResult method, then Android OS will automatically start activity A again and pass the result to it.

However, if the activity A is closed by calling finish method, or if it wiped out of memory by user, then when Activity B calls setResult, and Android OS will ignore the result, and will not create Activity A to receive the result.

Friday, April 13, 2018

Debug ios and Android application using wifi connection

The usb connector on android or iOS device may wear out after a while, and the connection to device gets dropped frequently when rotating the device. Or the mac may not have enough USB port to connect all devices. In those cases, it is useful to use wifi connection for debug


Android
Android studio supports wiki connection to device for debugging, and it can be done with below steps:
1. connect device using USB connection
2. set the device to listen for a tcpip connection on port 5555 from terminal
adb tcpip 5555
3. disconnect the device usb connection
4. find the device ip address from device's Settings > About tablet (or About phone) > Status > IP address screen
5. connect the device using tcpip connection
adb connect YourDeviceIPAddresss
6. confirm the device is connected using wifi connection
adb devices
7. start the debug from android studio and your device will be ready to use in same way as usb connection.

Note when you start Android Device monitor, it will disconnect the device connected by wifi. In that case, you will need to run
adb connect YourDeviceIPAddresss
again to reconnect to your device.

In addition, the below adb command can be used to let application wait to start until a debugger is attached. It is useful to debug the app starting code

adb shell am set-debug-app -w com.mypackage.myapplication


iOS
It is much simple to do wifi debug on iOS
1. First connect the device via USB to MAC
2. Open Device and Simulator window under Xcode's Window menu
3. select your device, and then check the "Connect via network" checkbox
4, unplug the USB cable



Tuesday, April 10, 2018

Understanding Android PendingIntent

To understand PendingIntent, first needs to see how activity is started by regular intent.

When using regular intent to start an activity, it requires two pieces of information, the first one is class name of activity to be started; the second one is context for calling the startActivity method.

In the below code Activity1 uses regular intent to starts Activity3
Intent in = new Intent(this, Main3Activity.class);
this.startActivity(in);

Now let think a case when Activity1 wants to let others to start Activity3, but still on Activity1's context (or permission scope). To do so, Activity needs to wrap the above two information in a PendingIntent object as below
Intent in = new Intent(this, Main3Activity.class);
PendingIntent pendingInt = PendingIntent.getActivity(this, 0, in, 0); 
Since PendingIntent has the full information (context and activity class name), so any caller (activity or a plain thread) can use it to start the target activity by calling the below code without even knowing who originally created the pending intent. 
pendingInt.send();
The way of passing the PendingIntent to other Activity or thread does not matters, it can be passed simply as a function parameter, or passed as Intent's Parcelable extra. Note the PendingIntent object can be used repeatedly by other activity and thread, for the purpose of passing the information to the target activity.

A typical use case of PendingIntent if notification. Assume there is an internal activity A in your app. which is not visible to external apps. Then in your app, creating a notification, when it is tapped by user, using PendingIntent to launch Activity A. Here the PendingIntent is used to tell Android OS, that when user taps the notification item, launching the activity A on your behalf, no matter your is running or not. Without using PendingIntent, this cannot be done, as the Activity is only visible to your app, Android OS and other apps does not even know its existence.  
public void onSetNotification(View v){
    Intent intent = new Intent(this, SecondActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, "My Notification CHANNEL_ID")
            .setSmallIcon(R.drawable.ic_stat_account_balance)
            .setContentTitle("My notificaiton Title")
            .setContentText("My notificaiton text Content")
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
            .setStyle(new NotificationCompat.BigTextStyle()
                    .bigText("Much longer text that cannot fit one line. long long long long long long long long long long long long "))
            .setPriority(NotificationCompat.PRIORITY_DEFAULT);
    NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

    notificationManager.notify(notificationId, mBuilder.build());
}

Monday, April 9, 2018

How background activity handles configuration change on Android

It is well known that when configuration changes, activity will immediately call onDestroy and then onCreate to handle the new configuration.

But that only happens for the foreground activity. Any background activities will not handle the configuration change immediately when it happens. Instead, when the background activity was brought into foreground again, it will first calls onDestroy method and then calls onCreate method to handle the new configuration, in the similar way as the foreground activity. The intent that used to start the activity will also be available to initialize the activity when onCreate is called again for configuration change, however, as the activity is created from scratch, so the previous state information will be lost and need to to set again by application logic. 


Note onDestroy method cannot be used to check memory leak. For example, after calling finish() on the activity, if the activity is still referenced by a background thread, then although onDestroy is called on the activity, the activity object cannot be collected by GC, and will cause a memory leak. 

Sunday, April 8, 2018

Running methods in work thread for Android application

Android application has few ways to invoke method asynchronously, Thread, AsyncTask, Looper/Handler works in the same process. Service can cross process boundary.

1. thread
The most basic way to call method in a work thread is creating a thread object, the below sample creating a new work thread to do some work, and then calling view.post method to update UI in the main thread.

new Thread(new Runnable() {
    public void run() {
        final String workThreadInfo = "Work thread: " + getThreadSignature();
        txtOutput.post(new Runnable() {
            public void run() {
                String uiThreadInfo = "UI thread post: " + getThreadSignature();
                txtOutput.append("\r\n" + workThreadInfo);
                txtOutput.append("\r\n" + uiThreadInfo);
            }
        });
    }
}).start();

2. AsyncTask
AsyncTask standardizes the logic to create a work thread and then update the ui with the result, the above thread sample can be implemented using AsyncTask as below
new AsyncTask<Void, Void, Void>() {
    String workThreadInfo;

    @Override
    protected Void doInBackground(Void... voids) {
        workThreadInfo = "Work thread: " + getThreadSignature();
        return null;
    }

    @Override
    protected void onPostExecute(final Void result) {
        String uiThreadInfo = "UI thread post: " + getThreadSignature();
        txtOutput.append("\r\n" + workThreadInfo);
        txtOutput.append("\r\n" + uiThreadInfo);
    }

}.execute();

3. Handler/Looper
Android uses MessageQueue, Looper and Handler to handle messages. By default, new work thread just created does not have a Looper associate with it, the thread can call Looper.prepare to start the looper.

A thread can have only only one Looper which manages a single message queue by Handler to consume the messages. Multiple Handler can associate with a Looper to consume messages, Handlers are automatically added into the current thread's looper if looper is not specified when it is constructed. Messages are sent or post to message queue by calling handler's method and that message will be processed by that particular handler .

Message can be sent/post to message queues from multiple threads by calling Handler's method, and the only single Looper thread dispatches and consumes the message. So part of Handler's logic (send/post) is running in producer threads, and part of Handler's logic (process) is running in consumer's Loop thread.

Handler.handleMessage does not handle runnable, as dispatchMessage will direct run the runnable by itself. In addition, unlike runnable, the same message cannot be post/sent to different handler.

LooperThread mLooperThread;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Log.e(TAG, "onCreate: " + getThreadSignature() );
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
     mLooperThread = new LooperThread();
    mLooperThread.start();
}

class LooperThread extends Thread {

    public Handler mHandler;
    public Handler mHandler2;

    public void run() {
        Log.e(TAG, "Looper thread run: " + getThreadSignature() );

        Looper.prepare();
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) { //only msg will get here
                Log.e(TAG, "handleMessage: " + msg.toString() );
                doLongRunningOperation(msg);
            }

            @Override
            public void dispatchMessage(Message msg) {  //both runnable and msg will get here
                Log.e(TAG, "dispatchMessage: " + msg.toString() );
                super.dispatchMessage(msg);
            }
        };

        mHandler2 = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Log.e(TAG, "handleMessage: " + msg.toString() );
                    doLongRunningOperation(msg);
            }

            @Override
            public void dispatchMessage(Message msg) {
                Log.e(TAG, "dispatchMessage: " + msg.toString() );
                super.dispatchMessage(msg);

            }
        };

        Looper.loop();
    }


    private void doLongRunningOperation(Message msg) {
        Log.e(TAG, "doLongRunningOperation in handler: what " + Integer.toString(msg.what) );
        Log.e(TAG, "doLongRunningOperation in handler: " + getThreadSignature() );
    }
}

public void onLooperHandlerClicked(View v) {
    Log.e(TAG, "onLooperHandlerClicked: " + getThreadSignature() );
   // Message msg= mLooperThread.mHandler.obtainMessage();
    Message msg = Message.obtain();
    msg.what = 100;
    mLooperThread.mHandler.sendMessage(msg);

    Runnable task = new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, "runnable called at: "+  getThreadSignature());
        }
    };

    Message msg2 = mLooperThread.mHandler.obtainMessage();
    msg2.what = 200;
    mLooperThread.mHandler2.sendMessage(msg);
    mLooperThread.mHandler2.post(task);
    
}

HandlerThread implements the above logic, and can be used by only implementing the handler logic.

4. Service
Android Service is another option to handle async method, the caller calls a service provider to perform a task.
IntentService is a derived class of generic Service, which performs a service request called with startService, and then stops itself automatically.
Generic service will keep the service live when startService is called by caller and the service will stay there until stopService is called by caller.
Service can also implement bind, so it can expose a complex interface for caller to invoke