问题
I have to use a native library (this is not my decision). The library already has a JNI wrapper, and the example Android NDK code works. But the library initialization routines return native handles and the developer is required to correctly close them.
Now, there's an interesting question: where to call the close_handle routines from?
At least in theory, each incorrect termination may result in temporary files left somewhere on the disk or some other kind of resource leak.
Library initialization takes from 0.5 to 1 sec and consumes a lot of memory.
An Activity
is a controller (in MVC sense), Android can kill it because of its own reasons, including turning the device, and the only function that is guaranteed to be called is onPause()
. So onPause
/onResume
is a bad place for a long resource-consuming operation.
(I know about android:configChanges="keyboardHidden|orientation"
, but I would prefer a solution that does not need it.)
Application
would be an ideal candidate (I consider the library to be a part of the model), but there is no "application terminates" event.
A Service
sounds promising, that native library is a service, but I just do not see how I can have the desired behavior: the handles must be closed when the application terminates.
Timeouts: sounds like a trade-off, but in fact it guarantees that the memory will not be available the moment it is needed but will become available a few seconds later.
回答1:
For now, it looks like I indeed need a service, but not exactly IntentService
: IntentService
calls stopSelf()
, while my service should hang around.
It's interesting that Service.onDestroy()
is called when the user chooses "stop" from "Running services" but not when the user chooses "Force stop" from "Applications". "Stop Debugging" also does not cause Service.onDestroy()
invocation.
EDIT:
My current solution is to use a class derived from a custom Service
subclass; the code has been borrowed from the source of IntentService
found somewhere in the 'net. In the derived subclass I override onDestroy()
and believe that's the best application termination notification available.
package com.xyz.customandroid;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
public abstract class HangAroundIntentService extends Service {
/** An extra with this name and the value of boolean true marks an intent as a cancel intent. See {@link #markedAsCancelIntent(Intent)}. */
private static final String CANCEL_MARK = "com.xyz~.customandroid.HangAroundIntentService.cancelQueue()";
private static final int WHAT_MAGIC = 0; // the "what" field for messages
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private boolean mHangAround = true;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
if (!mHangAround) {
stopSelf(msg.arg1);
}
}
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public HangAroundIntentService(String name) {
super();
mName = name;
}
/**
* Remove all pending messages from the handler queue.
* Processing of the message already fetched from the queue
* is not terminated by this function.
*
* Although this function is public, it is recommended
* to use the cancel intents instead.
* see {@link #markedAsCancelIntent(Intent)} and {@link #isCancelIntent(Intent)}.
*/
public void cancelQueue() {
mServiceHandler.removeMessages(WHAT_MAGIC);
}
/**
* Sets intent redelivery preferences. Usually called from the constructor
* with your preferred semantics.
*
* <p>If enabled is true,
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_REDELIVER_INTENT}, so if this process dies before
* {@link #onHandleIntent(Intent)} returns, the process will be restarted
* and the intent redelivered. If multiple Intents have been sent, only
* the most recent one is guaranteed to be redelivered.
*
* <p>If enabled is false (the default),
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
* dies along with it.
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
/**
* If enabled is true (default), the service does not stop after processing an intent.
*/
public void setServiceHangAround(boolean enabled) {
mHangAround = enabled;
}
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
/**
* If intent is a cancel intent, {@link #cancelQueue()} is invoked immediately;
* no other action is done for a cancel intent, whatever information it might contain.
*
* Intents that are not cancel intents are queued
* to be seen from {@link #onHandleIntent(Intent)}.
*/
@Override
public void onStart(Intent intent, int startId) {
if (isCancelIntent(intent)) {
cancelQueue();
} else {
Message msg = mServiceHandler.obtainMessage(WHAT_MAGIC);
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
*/
protected abstract void onHandleIntent(Intent intent);
/**
* Mark an Intent as cancel intent. The Intent will not be placed in the queue;
* instead, it will cause immediate cleaning of the queue
* (unless you redefine {@link #onStart(Intent, int)} in a derived class).
* @param intent to be modified
* @return the original intent after modification
*/
public static Intent markedAsCancelIntent(Intent intent) {
intent.putExtra(CANCEL_MARK, true);
return intent;
}
/**
* Check if the intent has been marked as a cancel intent.
* @param intent to be checked
* @return true if it is a cancel intent
*/
public static boolean isCancelIntent(Intent intent) {
return intent.getBooleanExtra(CANCEL_MARK, false);
}
}
and my service class is defined as:
public class MyService extends HangAroundIntentService {
public MyService() {
super("MyService");
}
public void onDestroy() {
MyData.getMyData().shutdown();
super.onDestroy();
}
// service-specific static methods not shown
}
To be continued...
回答2:
In practice, it turned out that some native library leaks resources; I had to do a process restart (this did not kill the activity, but re-created the process):
how to programmatically "restart" android app?
One can only hope that no library leaks temporary disk files...
来源:https://stackoverflow.com/questions/10313691/when-should-i-free-the-native-android-ndk-handles