2015년 12월 20일 일요일

Android ANR Debugging


In Android, application responsiveness is monitored by the Activity Manager and Window Manager system services.
and android will display the Application Not Responding (ANR) dialog for a particular application when it detects one of the following conditions:
  • No response to an input event (within 5 seconds.
  • A BroadcastReceiver hasn't finished executing within 10 seconds.


In case ANR happens, through the method dumpStackTraces() of ActivityManagerService all the information of the thread stack will be saved in /data/anr/ directory.

Then, what we need to find or look at to ?
The following is the testing log snippet...basic investigation steps would be to look for a pattern like "waiting to lock........held by tid=xxx"..
Here Thread ID 12 is hold the task.. so we need to look more closely the problematic method.



"main" prio=5 tid=1 MONITOR
| group="main" sCount=1 dsCount=0 obj=0x410a8c40 self=0xa73c98
| sysTid=704 nice=0 sched=0/0 cgrp=default handle=1075004808
| schedstat=( 0 0 0 ) utm=31475 stm=3384 core=1
  at com.test.studio.TestNativeSAP.synchronizedInvoke(TestNativeSAP.java:~246)
  - waiting to lock <0x42472270> (a com.test.studio.TestNativeSAP) held by tid=12 (AsyncTask #2)

"AsyncTask #2" prio=5 tid=12 NATIVE
  at com.test.studio.TestNativeSAP.startPreview(Native Method)
  at com.test.studio.TestNativeSAP.synchronizedInvoke(TestNativeSAP.java:286)
  at com.test.studio.TestNativeSAP.invokeStartPreview(TestNativeSAP.java:169)

This is the only example.. trace does not always contain "waiting to lock"
so sometimes it is really hard to find main reason.



Thread States:
- running: executing application code
- sleeping: called Thread.sleep()
- monitor: waiting to acquire a monitor lock
- wait: in Object.wait()
- native: executing native code
- vmwait: waiting on a VM resource
- zombie: thread is in the process of dying
- init: thread is initializing
- starting: thread is about to start


Android Testing Library


Android testing support library includes the followings:
1) AndroidJunitRunner: JUnit4 compatible test runner for Android
2) Espresso: UI testing framework, suitable for functional UI testing within an app
3) UI Automator: UI testing framework, suitable for cross-app functional UI testing


1) AndroidJunitRunner
- Looks really familiar with Junit, but should avoid mixing JUnit3 and JUnit4
- For use JUnit4, must use @RunWith(AndroidJUnit4.class) annotation


import android.support.test.runner.AndroidJUnit4;
import android.support.test.runner.AndroidJUnitRunner;
import android.test.ActivityInstrumentationTestCase2;

@RunWith(AndroidJUnit4.class)
public class CalculatorInstrumentationTest
        extends ActivityInstrumentationTestCase2 {

    @Before
    public void setUp() throws Exception {
        super.setUp();

        // Injecting the Instrumentation instance is required
        // for your test to run with AndroidJUnitRunner.
        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
        mActivity = getActivity();
    }

    @Test
    public void typeOperandsAndPerformAddOperation() {
        // Call the CalculatorActivity add() method and pass in some operand values, then
        // check that the expected value is returned.
    }

    @After
    public void tearDown() throws Exception {
        super.tearDown();
    }
}



2) Espresso
In order for the Android Plug-in for Gradle to correctly build and run your Espresso tests,
you must specify the following libraries in the build.gradle file of your Android app module:

dependencies {
    androidTestCompile 'com.android.support:support-annotations:23.0.1'
    androidTestCompile 'com.android.support.test:runner:0.4.1'
    androidTestCompile 'com.android.support.test:rules:0.4.1'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
    // Set this dependency if you want to use Hamcrest matching
    androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
}

Steps for writing codes are easy, check the below steps.
- Find the UI component to test in an Activity by calling onView(), onData()
- Simulate a specific user interaction by calling ViewInteraction.perform(), DataInteraction.perform()
- Make sure everything works as expected

// find a view by looking for a text string it displays 
onView(withText("Sign-in"));

// find a view with resource id
onView(withId(R.id.button_signin));

// allOf() combines multiple matchers together
// find a view that has resource id of button_signin and contain the string "Sign-in"
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));

// with not keyword can filter those views do not matchers
// find a view that has resource id of button_signin but do not contain the string "Sign-in"
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));


In an AdapterView widget, the view is dynamically populated with child views at runtime. If the target view you want to test is inside an AdapterView (such as a ListView, GridView, or Spinner), the onView() method might not work because only a subset of the views may be loaded in the current view hierarchy.

Instead, call the onData() method to obtain a DataInteraction object to access the target view element.
onData(allOf(is(instanceOf(Map.class)),
        hasEntry(equalTo(LongListActivity.ROW_TEXT), is(str))));


The ViewActions class provides a list of helper methods for specifying common actions. You can perform actions like:
- ViewActions.click(): Clicks on the view.
- ViewActions.typeText(): Clicks on a view and enters a specified string.
- ViewActions.scrollTo(): Scrolls to the view. The target view must be subclassed from ScrollView
- ViewActions.pressKey(): Performs a key press using a specified keycode.
- ViewActions.clearText(): Clears the text in the target view.

Lastly call the ViewInteraction.check() or DataInteraction.check() method to assert that the view in the UI matches some expected state.

// Check that the text was changed.
    onView(withId(R.id.textToBeChanged))
            .check(matches(withText(STRING_TO_BE_TYPED)));


3) UI Automator
In order for the Android Plug-in for Gradle to correctly build and run your UI Automator tests, you must specify the following libraries in the build.gradle file of your Android app module:
dependencies {
    androidTestCompile 'com.android.support:support-annotations:23.0.1'
    androidTestCompile 'com.android.support.test:runner:0.4.1'
    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
    // Set this dependency if you want to use Hamcrest matching
    androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
}


// Initialize UiDevice instance
private UiDevice mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

// find Cancel/OK button object
UiObject cancelButton = mDevice.findObject(new UiSelector()
        .text("Cancel"))
        .className("android.widget.Button"));
UiObject okButton = mDevice.findObject(new UiSelector()
        .text("OK"))
        .className("android.widget.Button"));

// Simulate a user-click on the OK button, if found.
if(okButton.exists() && okButton.isEnabled()) {
    okButton.click();
}


// With UiSelector, it is possible to access a specific UI component in an app
// the first list view in the currently displayed UI
// then find UI element with the text property "Apps"
UiObject appItem = new UiObject(new UiSelector()
        .className("android.widget.ListView")
        .instance(1)
        .childSelector(new UiSelector()
        .text("Apps")));

Once your test has obtained a UiObject object, you can call the methods in the UiObject class to perform user interactions on the UI component
represented by that object. You can specify such actions as:

- click(): Clicks the center of the visible bounds of the UI element.
- dragTo(): Drags this object to arbitrary coordinates.
- setText(): Sets the text in an editable field, after clearing the field's content.
- swipeUp(): Performs the swipe up action on the UiObject.
Similarly, the swipeDown(), swipeLeft(), and swipeRight() methods perform corresponding actions.


Lastly use standard JUnit Assert methods to test that UI components in the app return the expected results.

private static final String CALC_PACKAGE = "com.myexample.calc";

public void testTwoPlusThreeEqualsFive() {
    // Enter an equation: 2 + 3 = ?
    mDevice.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("two")).click();
    mDevice.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("plus")).click();
    mDevice.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("three")).click();
    mDevice.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("equals")).click();

    // Verify the result = 5
    UiObject result = mDevice.findObject(By.res(CALC_PACKAGE, "result"));
    assertEquals("5", result.getText());
}


Reference:
Android Testing Support Library

2015년 12월 16일 수요일

SVG in Android


I believe vector graphics native supports for android had long been on the wish list and good news(?) is that from the release of API 21 (Android 5.0) VectorDrawable has been included. if used properly I don't think we have to prepare all the images for each different device resolutions, however still there exist some problems.
Android supports only basic level of vector graphics..

  • VectorDrawable supports paths using the ‘d’ attribute like the SVG format
  • VectorDrawable does not read/support the SVG format
  • VectorDrawable does not support gradients
  • VectorDrawable does not support numbers using scientific E notation (as commonly used in SVG)


Even though SVG format is not supported, we can still convert SVG files to VectorDrawables with Android Studio and open source tools like Inkscape, svg2android

Menu in Android Studio:
File > New > Vector Asset > Local Asset file > change options if required

Link:
Inkscape: https://inkscape.org/
svg2android: http://inloop.github.io/svg2android/


Maybe SVG not only helps to save some time for development, but also increase the visual quality, we need to be careful of using it, because it is rendered at runtime, so always need to keep the complexity of SVG files to minimum.

2015년 12월 7일 월요일

How to use LRU cache in Android


Android provides LRU cache, that is to evict the least recently used item from the data.
https://developer.android.com/reference/android/util/LruCache.html

1. The size of the cache can be adjusted like the following (depends on the purpose of having cache and the resolution of the devices):
// Cache Size 
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int availableMemoryInBytes = am.getMemoryClass() * 1024 * 1024;

// Need to adjust size
LruCache bitmapCache = new LruCache(availableMemoryInBytes / 8); 


2. In the example we need to override sizeOf() methods with bitmap size
public class MyCache extends LruCache(String, Bitmap) {
    @Override
    protected int sizeOf(String Key, Bitmap value) {
        // return the size of the in-memory bitmap
        // counted against maxSizeBytes
        return value.getByteCount();
    }
    ...
}      


3. The usage of the LRU cache:
Bitmap bitmap = mCache.get(filename);
if (bitmap == null) {
    bitmap = BitmapFactory.decodeFile(filename);
    mCache.put(filename, bitmap );
}

2015년 10월 16일 금요일

7 Mobile development skills

I have just read an article about the skills that can make my salary a little bit higher.

Event if it is true or not, reading this kind of writings make me awake to find out some trends of industry (?)...


7 mobile development skills that mean higher pay By Sarah K. White

According to the article, the following is the list of the skills you need to make your salary even higher.

1. Apache Cordova

2. F#

3. Grails

4. Drupal

5. iRise

6. Oracle Certified Professional Java SE Programmer

7. jBoss Certified developer

Some of them are really familiar to me, but some of them are not,
so I will go and check some skills, if it turns out to be really interesting, why not study

2015년 2월 2일 월요일

How to set your app to open a file in Google Drive

To make sure your app is included in the list of apps that can open a file in Google Drive app, you need to make some additions to your AndroidManiest.xml. The activity for opening Drive files & MIME Types that activity can open must be specified.
The following example code shows the manifest for "ViewerActivity". Your own app manifest must include a similar intent with your own appropriate values and MIME types that your app can open:

<activity
android:name="ViewerActivity"
android:label="@string/my_viewer"
android:icon="@drawable/app_icon"
android:exported="true">

<intent-filter>
<action android:name="com.google.android.apps.drive.DRIVE_OPEN">
<data android:mimetype="image/png">
<data android:mimetype="image/jpeg">
<data android:mimetype="image/jpg">
</intent-filter>
</activity>




Reference:
https://developers.google.com/drive/android/java-client




2014년 9월 12일 금요일

Signing in with Google+ for Android

First step is to go to the Google Developers Console (https://console.developers.google.com/project) and create project as well as Client ID.



Second step would be adding some permissions in AndroidManifest.xml to access Google+ API, account name as part of sign in, OAuth 2.0 tokens or invalidate tokens to disconnect. The following permissions should be requested.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />

Now we're ready to add some codes to initialize GoogleApiClient.
Some might wonder what GoogleApiClient really does, it basically wraps a ServiceConnection to Google Play services, this GoogleApiClient object is used to communicate with Google+ API and becomes functional after the asynchronous connection has been established with the service, and typically GoogleApiClient is managed like this:
- initialize GoogleApiClient in Activity.onCreate()
- invoke GoogleApiClient.connect() during Activity.onStart()
- invoke GoolgeApiClient.disconnect() during Acitivity.onStop()


import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.plus.Plus;

public class PlusActivity extends Activity 
        implements ConnectionCallbacks, OnConnectionFailedListener {

 /* Request code used to invoke sign in user interactions. */
 private static final int RC_SIGN_IN = 0;

 private GoogleApiClient mGoogleClient;

 private ProgressDialog mConnectionProgressDialog;

 /*
  * A flag indicating that a PendingIntent is in progress and prevents us
  * from starting further intents.
  */
 private boolean mIntentInProgress;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  mGoogleClient = new GoogleApiClient.Builder(this).addApi(Plus.API)
    .addScope(Plus.SCOPE_PLUS_LOGIN).addConnectionCallbacks(this)
    .addOnConnectionFailedListener(this).build();

  mConnectionProgressDialog = new ProgressDialog(this);
  mConnectionProgressDialog.setMessage("Signing in...");
 }

 @Override
 protected void onStart() {
  super.onStart();
  mGoogleClient.connect();
 }

 @Override
 protected void onStop() {
  super.onStop();

  if (mGoogleClient.isConnected()) {
   mGoogleClient.disconnect();
  }
 }

 @Override
 protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
  if (requestCode == RC_SIGN_IN) {
   mIntentInProgress = false;

   if (!mGoogleClient.isConnecting()) {
    mGoogleClient.connect();
   }
  }
 }

 @Override
 public void onConnectionFailed(ConnectionResult connectionResult) {
  Toast.makeText(this, "GoogleAPIClient Failed to Connect", Toast.LENGTH_SHORT).show();

  if (!mIntentInProgress && connectionResult.hasResolution()) {
   try {
    mIntentInProgress = true;
    startIntentSenderForResult(connectionResult.getResolution().getIntentSender(), RC_SIGN_IN, null, 0, 0, 0);
   } catch (SendIntentException e) {
    mIntentInProgress = false;
    mGoogleClient.connect();
   }
  }
 }

 @Override
 public void onConnected(Bundle bundle) {
  Toast.makeText(this, "GoogleAPIClient Connected", Toast.LENGTH_SHORT).show();
  mConnectionProgressDialog.dismiss();
 }

 @Override
 public void onConnectionSuspended(int arg0) {
  Toast.makeText(this, "GoogleAPIClient Connection Suspended", Toast.LENGTH_SHORT).show();
  mGoogleClient.connect();
 }
}

This is it, we done signing in Google+, will try to add some codes for accessing Google+ API to post some comments, retrieving friends list next time.