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

Monday, March 19, 2018

Resign ipa file for running app created by other developers

Sometimes you will need to run an ipa file created by other developers on your device, in order to do so, you will need to resign the ipa file with your ios developer certificate and profile. The below are steps to do so by assuming the app name is my.app

1. rename ipa file to zip file and unzip it

2. in terminal app, cd into folder which contains zip file

3. run
rm -r "Payload/my.app/_CodeSignature"  
to delete current code sign signature. Assume the ipa file name is my.ipa

4. download you signing profile from apple dev website and then use it to replace the one in ipa file
cp "mynewprovisionprofile.mobileprovision" "Payload/my.app/embedded.mobileprovision"
The file name must be changed to embedded.mobileprofile

5. replace appid with your app id specified in your signing profile
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.sap.fiori.client.me" ./Payload/my.app/Info.plist
Assume the appid specified in the signing profile is "com.sap.fiori.client.me"

5. copy an entitlement file from your own xcode project that uses the same appid you will sign the ipa file. The entitlement file is in xcode derivedData folder's Build/Intermediates.noindex/YourAppName.build/Debug-iphoneos/YourAppName.app.xcent.
Copy this file to the folder containing the ipa file, and rename it to entitlements.xml for easy to use

6. if you want to update any resource files included in the original ipa, like plist file or image resource files, you can do it now.

7. Resign the content again with the new entitlement and sign cert
 /usr/bin/codesign --entitlements entitlements.xml -f -s "iPhone Developer: Haiquan Li (WV2JN948JT)" "./Payload/my.app"
you can get your signing cert name from keychain utility app

8. zip the payload folder to get the new ipa file
 zip -qr "my.resigned.ipa" Payload

The above steps can also be executed by using the below script.
#!/bin/sh
#To run the script, first copy provision profile embedd.mobileprovision and entitlement.xml to the 
#same folder as ipa file is stored
#then run the below command 
#sh ThisShellFileFullPath IPAFileFullPath OptionalNewAppID
#for example to resign the ipa using new bundle id of com.sap.fiori.client.me
#bash /Users/i826633/Documents/Jonathan/git826633/tools/IPA\ re-sign/resign.sh /Users/i826633/Documents/Bugs/359459passcode/FioriClient_release-1.11.2-Release-iphoneos.ipa com.sap.fiori.client.me
echo "ios ipa resign"
IPAFilePath="$1"
echo "IPA file path: $IPAFilePath"
  
IPAFileName=$(basename "$IPAFilePath" ".ipa")
echo "$IPAFileName"
ZipFileName=$IPAFileName".zip"
echo "$ZipFileName"
cp $IPAFilePath $ZipFileName
ZipFileFolder=$IPAFileName"zip"
echo "$ZipFileFolder"
tar xvf $ZipFileName
BundleFileName=$(ls ./Payload)
echo $BundleFileName
BundleName=$(basename "$BundleFileName" ".app")
echo $BundleName
#remove codesignature
rm -r "./Payload/""$BundleFileName""/_CodeSignature"
cp "embedded.mobileprovision" "./Payload/""$BundleFileName""/embedded.mobileprovision"

if [ "$2" ]; then
   /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $2" "./Payload/""$BundleFileName""/Info.plist"
else
   echo "No App ID needs to be changed"
fi

/usr/bin/codesign --entitlements entitlements.xml -f -s "iPhone Developer: Haiquan Li (WV2JN948JT)" "./Payload/""$BundleFileName"

zip -qr "$BundleFileName"".resigned.ipa" "./Payload"

Saturday, March 17, 2018

Include library module from external folder for android project

Usually the android library module is placed under the project root folder and can be easily reference from settings.gradle file using folder name. However, it requires some extra settings to use an library module from external folder.

Assuming we start with below folder structure which haves three android projects, each project has an app module and library module, the goal is to call method from mylib1 and mylib2 from the app module and mylib3 module in myapp3 project. Note mylib1 and mylib2 is not under the root folder of myapp3 project
myapp1
      app1
      mylib1 (mylib1 is a jar library, which contains a method calls myMethod1)
myapp2
      app2
      mylib2 (mylib2 is android aar library module, which contains a method calls myMethod2)
myapp3 
     app3
     mylib3 (Android library module)

In order to add modules from external folder, the app3's project settings.gradle file needs to be updated either with relative path or absolute path as shown below
include ':app', ':mylib3'
include ':mylib2'
    project(':mylib2').projectDir = new File('../myapp2/mylib2')
include ':mylib1'
    project(':mylib1').projectDir = new File('/Users/i826633/Documents/temp/app1/mylib1')

Once the external modules are added into the project app1, then to use the modules in app1's app module, the app1's module build.gradle needs to add the external modules in its dependencies section as below
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    compile project(path: ':mylib3')
    compile project(path: ':mylib2')
    compile project(path: ':mylib1')
}

Similarly, to call the methods in mylib1 and mylib2 from mylib3, the build.gradle of mylib3 needs to be updated to include the mylib1 and mylib2 as dependencies
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support:appcompat-v7:26.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    compile project(path: ':mylib2')
    compile project(path: ':mylib1')
}

By the way, if a project includes many library modules, adding the modules one by one to another project's settings.gradle file would be too troublesome. In that case, all modules in the library project can be imported to another project by using includeBuild, which just imports all library modules included in the settings.gradle to the new project as shown below
include ':myapp1', ':mylib2'
includeBuild '/Users/i826633/Documents/temp/app1' 

When includeBuild is used, be sure the distributionUrl in project's gradle-wrapple.properties is gradle-4.5.1 or above
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-all.zip