5

I have a file open on the SD card. When someone mounts the SD card, it winds up crashing my application. I am trying to register for the ACTION_MEDIA_EJECT broadcast event, and i receive that, but it seems like i'm getting that too late. By the time I get that, it's already crashed my application. Is there any way to get notified before crashing my application?

Added some very simple sample code. When I do this, it winds up crashing the service when I turn on USB (MSC mode).

Test.java

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    try {
        startService(new Intent(this, TestService.class));
        bindService(new Intent(this, TestService.class), serviceConnection, Context.BIND_AUTO_CREATE);
    } catch (Exception e) {

    }
}

protected TestService testService;
private ServiceConnection serviceConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        TestService.LocalBinder binder = (TestService.LocalBinder) service;
        testService = binder.getService();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        testService = null;
    }

};

TestService.java

@Override
public void onCreate() {
    super.onCreate();
    try {
        mFileOutputStream = new FileOutputStream("/sdcard/test2", true);
    } catch (Exception e) {
    }
}

@Override
public IBinder onBind(Intent intent) { return binder; }

public static class LocalBinder extends Binder {
    public static TestService getService() {
        return _this;
    }
}
user306517
  • 523
  • 8
  • 21
  • 1
    Well, how does it crash? Do you have a stack trace? – EboMike Feb 17 '11 at 19:12
  • it seems like the system just kills me. i even have an unhandled exception handler and try/catch around all my file writes, but it still kills me. Unfortunately, I don't get a call stack, and none of my exceptions are triggered (i.e. I'm not in the middle of a write when the sdcard is mounted). – user306517 Feb 17 '11 at 19:20
  • 2
    Is there anything in logcat at all? Any vaguely relevant lines that show the process being killed. – Nick Feb 17 '11 at 19:26
  • I see my service crashing (trying to be restarted), and then after it gets restarted, I get the event saying the media was ejected... – user306517 Feb 17 '11 at 19:37
  • in logcat I see this line: 02-17 11:32:48.168: WARN/ActivityManager(92): Killing processes unmount media at adjustment 2 02-17 11:32:48.168: WARN/ActivityManager(92): Killing ProcessRecord{44e3c5d8 9356:com.test/10077} (adj 2): unmount media – user306517 Feb 17 '11 at 19:45
  • See an open bug: http://code.google.com/p/android/issues/detail?id=11740 – AlikElzin-kilaka Jan 17 '12 at 14:53
  • Are you sure that your app didn't get installed to the SD card? – Kieran Aug 15 '11 at 01:27
  • @user306517 did you find the solution? I have the exact same problem :\ – Alexey Andronov Oct 20 '17 at 10:14

2 Answers2

8

It is killing your application because it is a documented design decision in android to kill any process holding an open handle to the sdcard when the sdcard needs to be unmounted from the device (for example to be USB mounted to a connected PC).

A file system which still has open file handles cannot be cleanly unmounted. At the operating system level, there's no way to revoke a file handle, short of killing the process to which it was granted, so that is what android ultimately does.

Chris Stratton
  • 39,853
  • 6
  • 84
  • 117
  • 1
    That's a bit unhandy... for example, when copying a large file from the SD into memory. Of course, the user is not likely to voluntarily unmount the SD card while transferring files, but still the user *can* do it... And in such cases one would like to show a nice little error message, instead of both the activity and service being swiped out of existence! Maybe trying to spawn a new process for the service would solve the problem in most cases... – JM Lord Feb 03 '16 at 15:43
2
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
    //access external file
}

Without knowing more about what exceptions are being thrown, this is about all I can recommend.

Also, the following code from the Environment class documentation may help:

BroadcastReceiver mExternalStorageReceiver;
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;

void updateExternalStorageState() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        mExternalStorageAvailable = mExternalStorageWriteable = true;
    } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        mExternalStorageAvailable = true;
        mExternalStorageWriteable = false;
    } else {
        mExternalStorageAvailable = mExternalStorageWriteable = false;
    }
    handleExternalStorageState(mExternalStorageAvailable,
            mExternalStorageWriteable);
}

void startWatchingExternalStorage() {
    mExternalStorageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i("test", "Storage: " + intent.getData());
            updateExternalStorageState();
        }
    };
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
    filter.addAction(Intent.ACTION_MEDIA_REMOVED);
    registerReceiver(mExternalStorageReceiver, filter);
    updateExternalStorageState();
}

void stopWatchingExternalStorage() {
    unregisterReceiver(mExternalStorageReceiver);
}
Jon Willis
  • 6,993
  • 4
  • 43
  • 51
  • the problem is that it's killing my application before I even get the first broadcastreceiver event. – user306517 Feb 17 '11 at 19:24
  • meaning... you're trying to read/write from the external file right after media is ejected, but before you're aware of it. Wrapping all calls I/O to the file with if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { //access external file } will fix that. – Jon Willis Feb 17 '11 at 20:08
  • ^since Environment.getExternalStorageState() is polling the filesystem. It is certainly less efficient, but may be necessary to prevent crashes in your case. – Jon Willis Feb 17 '11 at 20:09
  • 1
    well the problem is that I just have a file open on the SD card. I'm not actually writing to it. And I think simply the mere fact of having an open file on the SD card is causing the system to kill my application when the SD card is lost. wrapping all my write calls with this won't help that situation. – user306517 Feb 17 '11 at 21:53
  • You're reading from it then... or doing something. I just stuck a reference to an unmountable file in a service and unmounted, and nothing happened. Post some relevant code, or I won't be able to help you any further. – Jon Willis Feb 17 '11 at 22:19
  • keeping the mFileOutputStream open should be changed. open only for writing and close it right after... that should prevent the app from crashing... like jon said – WarrenFaith Feb 17 '11 at 23:45
  • that would certainly help a lot, but ultimately, there's a timing hole there, between the time I have the file open and the time that I can close it, if I lose the sd card, the app will crash... – user306517 Feb 17 '11 at 23:47
  • Yeah, but there is nothing more you can do about it then what Jon mentioned. The user is always the boss... – WarrenFaith Feb 17 '11 at 23:50
  • 1
    That seems problematic. It seems like it shouldn't crash until I try to write something to the sdcard (that isn't there). And even then, seems like it should throw an exception and let me catch it and handle it appropriately rather than just kill my app. The weird thing is that if i do this within an activity (not a service), it seems to behave as expected, but for some reason having it in a service is causing it to crash... – user306517 Feb 17 '11 at 23:54
  • You're keeping a class-level reference to the external file. That means that file's lifetime is bound to your service's lifetime. Android may be killing your service as a result of that. Its probably a bad idea to keep a class-level file reference if you can do it otherwise, anyway. – Jon Willis Feb 18 '11 at 00:46
  • okay, that's fair, but that means I can't do any file operations (of any length) in my service. If I need to process some file in some way, I risk the app crashing because the user went into msc mode. Seems bad... – user306517 Feb 18 '11 at 01:01
  • You can still catch exceptions. The reason you were being killed (to my understanding) wasn't because you were raising an exception, but that android didn't like what you were doing. Now that your service isn't being killed, you can continue on to try to access a file which isn't there, which you can handle. – Jon Willis Feb 18 '11 at 01:19
  • @Jon, @user306517: to get the broadcasts working, you'll need to `addDataScheme("file")` to the `IntentFilter`. – laalto May 25 '11 at 12:21