android

Setting up a Jenkins continuous integration environment for Android

The aim of this article will be to run through the steps needed to setup a Jenkins based CI environment to build and Android project, run unit tests, integration tests, etc. It will run through all of the tasks assuming that you have a clean Windows installation without even Java being installed.

  • Install JDK
  • Install SCM (Git/SVN)
  • Install Jenkins
  • Install the Android SDK
  • Install any useful Jenkins plugins
  • Create your first build target

Install JDK

At the time of writing this, JDK 1.8 is the latest version so I would recommend installing that on your server, however, if for any reason you cannot run with this JDK and then install the version that your project requires. Just head over to Oracle’s site, download and install it in the default location.

Install SCM

Git

Our project used git and initially I came from an SVN background so I found TortoiseGIT a useful client. This can be found here. It’s not the best client because it encourages you to use git in an SVN way but it does the job for us.

You will also need to install the git client application which you can get from here. However, if you start up TortoiseGIT you will be prompted almost straight away to do this. Download it and install it on your machine.

This is useful to have these tools on your machine to manually test the cloning of your builds etc. if you have issues when configuring the server.

Set the location of git in your system path

Jenkins needs to know where the git application is installed. The easiest and most useful way to do this is to add it to the system path. Click the start menu and type environment then select system environment variables from the search results. Then click Environment Variables and find Path and edit it. Assuming you installed git in the default location you should add ;C:\Program Files (x86)\Git\bin to the end of the path.

SVN

If you use SVN still, then it install your preferred SVN tools at this point.

Install Jenkins

So this is the main reason for this article, time to install Jenkins. You can find it here. Jenkins is one of the most popular CI servers for Java at the moment. It is a fork of the Hudson CI project so, if you are unfamiliar with these servers and you see some people talking about Hudson and some about Jenkins, these are effectively the same thing. The plugins between the servers are all compatible with both servers.

Screenshot 2015-04-22 15.41.15

This article is for Windows and therefore we’ll be installing the native package. This has the benefit that it comes with Windows Service wrappers so when the server restarts then Jenkins will automatically restart as well.

Now is the best time to update all of the plugins that come with Jenkins. To do this click on the Manage Jenkins link on the left hand side of http://localhost:8080/ then click Manage Plugins. Click Select All at the bottom of the screen and then click Download Now and install after restart. While it is downloading the plugins, check the Restart Jenkins when installation is complete and no jobs are running checkbox so that the plugins are updated.

Install Jenkins Plugins

There are a number of plugins that we’ll need for Android or are just recommended because they are really helpful in my opinion. To select these plugins:

Click Manage Jenkins

Click Manage Plugins

Click the available tab

Search for each of these plugins, click select and then install without restart. Some of the plugins will already be installed.

  • Android Emulator Plugin – This is required to be able to run the android emulator and run instrumented automated tests.
  • Git Client Plugin
  • Git Plugin
  • JaCoCo Plugin – This is useful for capturing code coverage information when running unit tests and any other automated tests.
  • JUnit Plugin – When running any JUnit tests in your build this plugin will allow Jenkins to report the results in a useful way.
  • Gradle Plugin – This allows Jenkins to run Gradle based builds which on modern Android projects will be a requirement.
  • JavaDoc Plugin – Use this plugin to provide JavaDoc output in your build of your source.

Install Android SDK

The next step we need to take before attempting to build anything is to download and install the android SDK and point Jenkins at it. Head over to the Android SDK site here and download the stand alone SDK. Install it using the default settings apart from make sure that it is available for all users so the service can access it.

Start up the SDK Manager and download the SDK that you need and any Extras that you might need. If you’re already building from an IDE like Android Studio you’ll already know what you need.

Once this is done you need to tell the OS where Android SDK is so that Jenkins can use the right location for building. To do this open up the Windows System Environment variables and create a new variable called ANDROID_HOME and set it to C:\Program Files (x86)\Android\android-sdk assuming that is where you installed it to.

Create your first build target

  • In the top level screen, click on New Item then enter the name of your project, select Freestyle Project and click OK.
  • Enter a description for the target.
  • Then enter the details for your SCM. I’m using git so I select git and enter the URL.
  • We also connect using a specific build account and password so we add that by clicking on the add button below the URL.
  • If you get the error Could not init C:\Windows\TEMP\hudson this is most likely because you haven’t added git to the system path for Jenkins to find it. Once you do this and restart the Jenkins service it will work.
  • Next you’ll need to setup up what to build. Under the Build panel, click the Add Build Step button.
  • Select Invoke Gradle Script, click on Use Gradle Wrapper
  • Enter  clean build into the Tasks field.

You’ll likely want to archive the artifacts of the build, i.e. keep the APK files for future use so

  • Click on Add Post Build Action
  • Enter **/*.apk into the

That’s it. You can of course create other targets in Jenkins to build JavaDoc, run unit tests and integration tests on code commit etc. but this will get you the base system working.

Don’t make network requests on the UI thread!

Today I was generating an HTTP request in an Android application and I got an exception that briefly confused me. The solution is an obvious one once I understood the reason and to be fair the exception is pretty self explanatory.

The code I has was the following, just to make an HTTP connection and check the file size of the file I was requesting.


  try {
    HttpURLConnection urlConnection = null;
    URL url = new URL("http://www.google.com");

    try {
      urlConnection = (HttpURLConnection) url.openConnection();
      urlConnection.connect();

      int fileLength = urlConnection.getContentLength();
    } catch (IOException exception) {
       // Handle exception
  } finally {
    if (urlConnection != null) {
       urlConnection.disconnect();
    }
  }
 } catch (MalformedURLException malformedUrl) {
   // Handle exception
}

The exception that I got from this code was


Caused by: android.os.NetworkOnMainThreadException
            at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1145)
            at java.net.InetAddress.lookupHostByName(InetAddress.java:385)
            at java.net.InetAddress.getAllByNameImpl(InetAddress.java:236)
            at java.net.InetAddress.getAllByName(InetAddress.java:214)
            at com.android.okhttp.internal.Dns$1.getAllByName(Dns.java:28)
            at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:216)
            at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:122)
            at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:292)
            at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
            at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
            at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
            at com.android.okhttp.internal.http.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:89)

The reason for this exception was because I was making a network request from a method that was invoked from a button click in an activity. What I should have done is created a new thread or AsyncTask to make this call. Most people will see this exception and understand immediately or not do such a daft thing in the first place, but if you are new to Android development then the solution may help others.


private class DownloadTask extends AsyncTask {
    @Override
    protected Void doInBackground(Void... sUrl) {
        try {
            HttpURLConnection urlConnection = null;
            URL url = new URL("http://www.google.com");

            try {
                urlConnection = (HttpURLConnection) url.openConnection();
                urlConnection.connect();

                int fileLength = urlConnection.getContentLength();
            } catch (IOException exception) {
                // Handle exception
            } finally {
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
            }
        } catch (MalformedURLException malformedUrl) {
            // Handle exception
        }
        return null;
    }
}

The task would then be invoked using the following.


new DownloadTask().execute();

How to use Robolectric and still make real HTTP requests

Recently, I have been writing some unit tests for my application using Robolectric. The framework and plugin has allowed me to write some plain unit tests using Mockito which is great, however, today I came across a feature that I didn’t want to take advantage of.

I was testing a class that is responsible for making REST HTTP requests to a server and for this test I wanted to get real responses. However, when running the tests in Android Studio everything was fine, but as soon as I ran it from the command line using gradle I got the following error.


java.lang.RuntimeException: Unexpected call to execute, no pending responses are available. See Robolectric.addPendingResponse(). Request was: GET http://127.0.0.1/rest/543e7ee26c3f52cce18c365f
	at org.robolectric.tester.org.apache.http.FakeHttpLayer.emulateRequest(FakeHttpLayer.java:127)
	at org.robolectric.shadows.ShadowDefaultRequestDirector.execute(ShadowDefaultRequestDirector.java:162)
	at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java)
	at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
	at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
	at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:83)
	at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:46)
	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:63)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:476)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:439)
	at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:259)
	at com.lsc.spear.services.REST.tasks.GetAssetByIdRequest.doInBackground(GetAssetByIdRequest.java:70...

This was down to a feature in Robolectric that allows developers to test their code without making real HTTP requests. See their blog article on this here

However, in my situation I wanted to make the real requests. You can turn this feature off using the method call.

Robolectric.getFakeHttpLayer().interceptHttpRequests(false);

I put it in my @Before annotated method so it was configured for all my tests.

How to generate JavaDoc for Android using Gradle

So, today I’ve been trying to get JavaDoc produced and I’ve come across a number of issues. Basically, there doesn’t seem to be a single place that just says do this. There are many stackoverflow answers, some work, some don’t then if you are running JDK 1.8 then there are other issues. So, I’ll show below how to generate JavaDoc, how to call it from Gradle (because I’m new to this and it wasn’t that obvious) and how to get around the JDK 1.8 issue.

Add the following configuration to your modules build.gradle file in your project. This is the one in the app folder if you are using a standard project setup.


android.applicationVariants.all { variant ->
    task("generate${variant.name.capitalize()}JavaDoc", type: Javadoc) {
        description "Generates Javadoc for $variant.name."
        source = variant.javaCompile.source
        ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
        classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar)
    }
}

To run this on the commandline then call ./gradlew generateDebugJavaDoc or ./gradlew generateReleaseJavaDoc

If you are running JDK 1.8 on your system you will get a whole pile of lint failures from your R.java file and since you can’t fix those then you have to work around the issue for now. This information was taken from http://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html

As you are using gradle it is simpler in my view to put this workaround in the gradle file so in the top-level project gradle file add the following.


if (JavaVersion.current().isJava8Compatible()) {
    allprojects {
        tasks.withType(Javadoc) {
            options.addStringOption('Xdoclint:none', '-quiet')
        }
    }
}

Conflict with Robolectric and SlideUpPanelLayout

The other day I was trying to integrate Robolectric with my project that uses the SlideUpPanelLayout. If I removed either Robolectric from the test build or SlideUpPanelLayout from my project it fixed the issue (temporarily, because I needed both) When using Robolectric and SlideUpPanelLayout together I get the following error.

Test failed to run to completion. Reason: ‘Instrumentation run failed due to 'java.lang.IllegalAccessError''. Check device logcat for details
Test running failed: Instrumentation run failed due to 'java.lang.IllegalAccessError'

This error is down to a Gradle configuration error. The reason is that there are dependencies declared multiple times in the build and one of them needs to be excluded. The challenge in this situation is knowing which dependency it is.

To work this out look at the exception which in this case ended with

Caused by: java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
at com.sothree.slidinguppanel.ViewDragHelper.<init>(ViewDragHelper.java:392)
at com.sothree.slidinguppanel.ViewDragHelper.create(ViewDragHelper.java:348)
at com.sothree.slidinguppanel.ViewDragHelper.create(ViewDragHelper.java:361)
at com.sothree.slidinguppanel.SlidingUpPanelLayout.<init>(SlidingUpPanelLayout.java:334)
at com.sothree.slidinguppanel.SlidingUpPanelLayout.<init>(SlidingUpPanelLayout.java:263)
... 26 more

ViewDragHelper was the culprit so if in your IDE (Android Studio) open the search for class name (Command-O in OS X) and enter ViewDragHelper. You will see that there are multiple Jars that are returned. You will need to exclude the one that you don’t want.

In this situation we had the android-19 sdk, support-v4-19 and support-v4-20 and library-2.0.1 (the slideuppanellayout library) to solve this I needed to exclude support-v4.

So in the build gradle file for the project where I included robolectric I needed to add the following to the list of excludes.

exclude module: 'support-v4'

This solved the problem!

NullPointerException when using Robolectric and Google Play Services

Today whilst writing a unit test for my Android application I had issues using Robolectric in my application. After a lot of hunting around and narrowing down the problem I found that it is was because of the declaration of Google Play Services in my AndroidManifest.xml file.

The error I got was

java.lang.RuntimeException: java.lang.NullPointerException
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:240)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:69)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:105)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:355)
at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
at org.robolectric.AndroidManifest$MetaData.init(AndroidManifest.java:698)
at org.robolectric.AndroidManifest.initMetaData(AndroidManifest.java:364)
at org.robolectric.res.builder.RobolectricPackageManager.addManifest(RobolectricPackageManager.java:364)
at org.robolectric.internal.ParallelUniverse.setUpApplicationState(ParallelUniverse.java:77)
at org.robolectric.RobolectricTestRunner.setUpApplicationState(RobolectricTestRunner.java:430)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:236)
... 35 more

The key parts of this stack trace being the NullPointerException and the fact it was coming from AndroidManifest.java. The cause of this issue was down to the fact in the AndroidManifest.xml I have this declaration to include Google Maps.


<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />

The part that is causing issues is the @integer/google_play_services_version and it is something to do with the fact the google_play_services_version is embedded in the Jar and it cannot be processed at the point it needs to be.

How to fix this?
To fix this issue you will can hard code it but that is a very hacky solution. It would be better to define this value locally but still point it at the correct value, for some reason this works.

So, in the values section of your project create a new resource file and call it something like values.xml.

In that file add the following code


<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="gms_version">@integer/google_play_services_version</integer>
</resources>

Then in your AndroidManifest.xml file where @integer/google_play_services_version was previously declared then put @integer/gms_version

This will work around the issue which is open here.

Solution for Test running failed: Unable to find instrumentation info for: ComponentInfo{…./com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner}

Whilst using Espresso I have come across an issue a number of times and each time it takes me a few minutes to remember what the solution is. So, I thought I should put it down here!

Solution

Ensure that both your build.gradle in the app folder and the run configuration in Android Studio need to declare the com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner.

In the build.gradle file in your app folder you need to add the line

testInstrumentationRunner “com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner”

In your android studio run configuration you will need to add com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner to the Specific Instrumentation Test Runner field.

Explanation

The com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner is a requirement when using Espresso and although the error message (Unable to find instrumentation info for: ComponentInfo{…./com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner}) is highlighting that the Activity might not have instrumentation set you’ll actually find that it is when you run the command 

 adb shell pm list instrumentation

I am using Android Studio for my development and therefore a lot of the solutions out there talk about modifying the test folders AndroidManifest.xml file. When using Android Studio and the Gradle build system this isn’t needed because the test AndroidManifest.xml is auto generated. As long as you specify the new Google Instrumented Test Runner in both of these locations the problem should resolve itself.