I have the following Go script (testapp.go) that I would like to keep running as a background service:
package main
import(
"net/http"
"time"
"log"
"golang.org/x/mobile/app"
)
func main() {
app.Main(func(a app.App) {
for {
req, err := http.NewRequest( "GET", "http://0.0.0.0:88/fetch_news", strings.NewReader("topic.title") )
if err != nil {
log.Print(err)
}
cli := &http.Client{}
res, err := cli.Do(req)
if err != nil {
log.Print(err)
} else {
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
returnStr := string(body)
// Do something with returnStr
}
time.Sleep(8 * time.Second)
}
})
}
My GoNativeActivity.java looks like this:
package org.golang.app;
import android.app.Activity;
import android.app.NativeActivity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyCharacterMap;
public class GoNativeActivity extends NativeActivity {
private static GoNativeActivity goNativeActivity;
public GoNativeActivity() {
super();
goNativeActivity = this;
}
String getTmpdir() {
return getCacheDir().getAbsolutePath();
}
int getRune(int deviceId, int keyCode, int metaState) {
try {
int rune = KeyCharacterMap.load(deviceId).get(keyCode, metaState);
if (rune == 0) {
return -1;
}
return rune;
} catch (KeyCharacterMap.UnavailableException e) {
return -1;
} catch (Exception e) {
Log.e("Go", "exception reading KeyCharacterMap", e);
return -1;
}
}
private void load() {
// Interestingly, NativeActivity uses a different method
// to find native code to execute, avoiding
// System.loadLibrary. The result is Java methods
// implemented in C with JNIEXPORT (and JNI_OnLoad) are not
// available unless an explicit call to System.loadLibrary
// is done. So we do it here, borrowing the name of the
// library from the same AndroidManifest.xml metadata used
// by NativeActivity.
try {
ActivityInfo ai = getPackageManager().getActivityInfo(
getIntent().getComponent(), PackageManager.GET_META_DATA);
if (ai.metaData == null) {
Log.e("Go", "loadLibrary: no manifest metadata found");
return;
}
String libName = ai.metaData.getString("android.app.lib_name");
System.loadLibrary(libName);
} catch (Exception e) {
Log.e("Go", "loadLibrary failed", e);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
load();
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, org.golang.app.GoNativeService.class);
startService(intent);
}
}
My GoNativeService.java looks like this:
package org.golang.app;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class GoNativeService extends IntentService {
@Override
public void onStart(Intent intent, int startId) {
Toast.makeText(this,"Service started. onStart()", Toast.LENGTH_SHORT).show();
handleCommand(intent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this,"Service started. onStartCommand()", Toast.LENGTH_SHORT).show();
handleCommand(intent);
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
}
My AndroidManifest.xml file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.golang.todo.testapp"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET" />
<application android:label="Testapp" android:debuggable="true">
<activity android:name="org.golang.app.GoNativeActivity"
android:label="Testactivity"
android:configChanges="orientation|keyboardHidden">
<meta-data android:name="android.app.lib_name" android:value="testapp" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="org.golang.app.GoNativeService"
android:enabled="true"
android:exported="true" />
</application>
</manifest>
This is my work environment:
GOARCH="amd64"
GOBIN="/root/go/bin"
GOCACHE="/root/.cache/go-build"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/root/go"
GOROOT="/usr/lib/go-1.10"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go-1.10/pkg/tool/linux_amd64"
This is what my $GOPATH/src/testapp directory looks like:
testapp/
assets/
icon.png
AndroidManifest.xml
testapp.go
This is what my $GOPATH/src/golang.org/x/mobile/app directory looks like:
app/
android.c
app_test.go
darwin_armx.go
GoNativeActivity.java
GoNativeService.java
internal/
x11.go
android.go
darwin_amd64.go
darwin_armx.m
shiny.go
app.go
darwin_amd64.m
doc.go
x11.c
How can I run my Go script as a background service on Android, so that when the app is exited (MainActivity closes), the background service will continue to fetch news messages?
Note: I am monitoring the requests at my webserver on port 88. I can run the script fine while GoNativeActivity
is active, but I would like to know how to build a native app that starts a background service.
Any answers or attempts to point me in the right direction would be appreciated!
Firstly, there's the common observation that you really shouldn't be doing what you ask. The correct way is to use AlarmManager to send a broadcast that wakes up the service for a single request. This way the system can better optimize power consumption. For doing this have your go code just process a single request and call it from the Java side whenever a broadcast is made.
However, to actually run the service persistently you will need to be starting a "foreground service" as described https://developer.android.com/guide/components/services. This in turn requires a Notification that is displayed whenever your service is running. In this case you may find the device powering itself off too, in which case you will need to look into acquiring a https://developer.android.com/reference/android/os/PowerManager.WakeLock . Doing this kind of thing can massively reduce battery life unless you are very careful.
I have found launching the go code in this way is sufficient for things like long running web servers on Android itself, including using websockets.