16

I have a system-privileged app with android:persistent=true in < application>. When I update it (via ADB or any other way), it fails to update properly and crashes.

What I'm seeing is that the system installs the update while the current (system-installed) version is still running. During the update the system does not stop the process (either attempts to stop and fails or doesn't try at all). After the update completes, the app seems to undergo a "restart" - I'm seeing components being initialized such as Application::onCreate() being called. But this is happening on same process as before the update!

Consequently (upon launching some activity of the app), the app crashes with "weird" exceptions such as failing to cast class to itself:

Caused by: java.lang.ClassCastException: com.XX.YY.ZZ.ClassName cannot be cast to com.XX.YY.ZZ.ClassName

While investigating, I saw that the ClassLoader used after the update does not refer to the the path of the updated APK, but remains pointing to the path of the original version:

Expected classloader:

dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.app.package-1/base.apk"],nativeLibraryDirectories=[/data/app/com.app.package-1/lib/x86_64, /data/app/com.app.package-1/base.apk!/lib/x86_64, /system/lib64, /vendor/lib64]]]

Actual classloader:

dalvik.system.PathClassLoader[DexPathList[[zip file "/system/priv-app/Appname.apk"],nativeLibraryDirectories=[/system/lib64/Start, /system/priv-app/Appname.apk!/lib/x86_64, /system/lib64, /vendor/lib64, /system/lib64, /vendor/lib64]]]

I'm assuming this is a result of not restarting the process during update.

Is there a way to update an app with persistent=true? Or is it an expected behavior, such app cannot be updated by the common update procedure (e.g. posting newer version on Google Play)?

user3118604
  • 854
  • 6
  • 12
  • does it work correctly from the new path if you restart the device after the app update? – Alex Cohn Jan 17 '18 at 22:20
  • 1
    Yes. Restarting the device solves the issue. The problem is that until device is rebooted the app crashes and keeps crashing in a loop since its process getting restarted . – user3118604 Jan 18 '18 at 08:47
  • Another question: after crash, does the app restart in with the same process id? – Alex Cohn Jan 18 '18 at 09:13
  • Have you seen this trick https://stackoverflow.com/a/38272684/192373? – Alex Cohn Jan 18 '18 at 09:16
  • After the crash, new process comes up (different ID). Same issue - incorrect classloader, fails on ClassCastException or similar (depends on which version of the app I'm upgrading to). Didn't try "forceStopPackage" - the app doesn't have the needed permission, but tried System.exit(0) to force process kill (successful). Process is killed, but the issue still occurs. – user3118604 Jan 18 '18 at 11:38
  • 1
    For commandline updates while developing you should likely do an `adb shell stop` first and an `adb shell start` after. For end-user updates, you may need to split your app into a truly system component updated via the *system* OTA mechanism applied by the recovery system when Android is not running, and a more everyday app component you can update via the play store. – Chris Stratton Jan 20 '18 at 21:59
  • Just curious, are there system apps with android:persistent=True running on new devices? How do I check that? – Sunil Mar 27 '18 at 19:49

4 Answers4

4

You cannot use adb install when working with apps that are in the system image, and apps that are persistent must be in the system image. What version of Android are you developing for? On recent versions of android I use one of methods described below. Before each you must run adb remount at least once.

Method 1: Works for code changes only, does not work for resource changes or manifest changes

Temporarily add this to your Android.mk:

LOCAL_DEX_PREOPT := false # Do not commit

Then perform a build with mm or the like.

Run the appropriate push command like so:

adb push $OUT/system/priv-app/MyApp/MyApp.apk /system/priv-app/MyApp/

Because the app is persistent you must kill the app process with the following command for it to pick up the changes:

adb shell ps | grep com.my.app | awk '{print $2}' | xargs adb shell kill

Do not forget to remove or comment out the change made to Android.mk before committing or making full builds.

Method 2: Necessary for anything other than simple java code changes

Perform a build with mm or the like.

Run the following commands:

adb sync
adb shell stop
adb shell start

Method 3

Just for completeness you could just build the entire tree and either flash the system or apply an OTA from the result.

satur9nine
  • 13,927
  • 5
  • 80
  • 123
  • The question originates for app update on user's phone (coming from Google Play). Your suggestion applies to developer's actions, not the end users. – user3118604 Jul 14 '18 at 11:22
0

You can use a simple hack and register to receive a "android.intent.action.PACKAGE_ADDED" intent for every application that is installed on the device. In case the application that is added has your package ID (indicating that your app was updated), you can try forcing it to finish and wait for the "persist" mechanism to re-lunch it as a new process.

Please note that I have been using persistent applications on multiple platforms and have never seen behavior such as the one you are describing (It might be helpful if you could provide the platform that you are using).

David Lev
  • 813
  • 5
  • 12
  • I can detect the upgrade situation without using android.intent.action.PACKAGE_ADDED. "Forcing it to finish" (e.g. killing the process or did you mean something else?) doesn't help, as the new process comes up with same "wrong" classloader. 2. I suspect, maybe MultiDex library is involved. Did you have successful experience in updating persistent apps compiled with multidex? – user3118604 Jan 24 '18 at 12:52
  • No. None of the apps we developed were compiled with MultiDex. – David Lev Jan 24 '18 at 21:21
0

Found a workaround for the issue. The update succeeds if the new version changes its process name

user3118604
  • 854
  • 6
  • 12
0
  <application
        android:name=".xyzApp"
        android:allowBackup="true"
    android:persistent="false">

Make persistent="false" in AndriodManifest.xml

Amruth A
  • 66
  • 5
  • 17