The app I'm working on is not in the Play Store, it is installed directly on devices used for specific projects. We do sign the apps using an in-house keystore. Updates are sent to the devices using our own web services, which download the updated APK and prompt the user to install the update as below.
Uri packageURI = Uri.parse("package:" + this.getPackageName());
Intent intent = new Intent(android.content.Intent.ACTION_VIEW, packageURI);
intent.setDataAndType(Uri.fromFile(new File(downloadPath + "/" + package_name)),
"application/vnd.android.package-archive");
this.startActivity(intent);
When we started using gradle build variants to allow for easy swapping of assets it was set up incorrectly as below:
productFlavors {
SampleProject {
dimension "project"
applicationId "companyID.SampleProject"
versionCode = "2"
versionName = "3.0.20180514.0"
resValue "string", "app_name", "Sample Project"
}
}
Note the incorrect assignment of version code and name and the fact that version code is a string.
This actually worked as-is until we got to a version code greate than 9 (double digits). This resulted in the (expected) error below.
Cannot cast object '10' with class 'java.lang.String' to class 'java.lang.Integer'
This lead to fixing the issue for projects going forward as below.
productFlavors {
SampleProject {
dimension "project"
applicationId "companyID.SampleProject"
versionCode 2
versionName "3.0.20180514.0"
resValue "string", "app_name", "Sample Project"
}
}
This works as expected, and lets us go above version code 10.
The issue is that this change does NOT allow updates to install over previous versions of the app built using the old incorrect method. When doing so the install fails almost immediately and simply displays "App not installed."
I have tested making sure nothing else has changed, using the same keystore and assets it still fails. The fix we have been using is to leave the version code at "9" and increment version name, but this isn't ideal.
Any suggestions would be appreciated. These projects tend to have a limited lifespan and not many updates, so it is not a major issue, but fixing it for existing projects would be nice so that we can continue to increment both.
EDIT Here is the error in Logcat when trying to install a signed APK with the corrected build variant.
05-14 12:00:51.785 3958-5291/? E/Parcel: Class not found when unmarshalling: com.android.packageinstaller.InstallFlowAnalytics
java.lang.ClassNotFoundException: com.android.packageinstaller.InstallFlowAnalytics
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:324)
at android.os.Parcel.readParcelableCreator(Parcel.java:2404)
at android.os.Parcel.readParcelable(Parcel.java:2358)
at android.os.Parcel.readValue(Parcel.java:2264)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2614)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.BaseBundle.getString(BaseBundle.java:920)
at android.content.Intent.getStringExtra(Intent.java:6200)
at com.android.server.am.ActivityStackSupervisor.startActivityLocked(ActivityStackSupervisor.java:2752)
at com.android.server.am.ActivityStackSupervisor.startActivityMayWait(ActivityStackSupervisor.java:2209)
at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:6401)
at com.android.server.am.ActivityManagerService.startActivity(ActivityManagerService.java:6173)
at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:170)
at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:4028)
at android.os.Binder.execTransact(Binder.java:453)
Caused by: java.lang.ClassNotFoundException: com.android.packageinstaller.InstallFlowAnalytics
at java.lang.Class.classForName(Native Method)
at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:324)
at android.os.Parcel.readParcelableCreator(Parcel.java:2404)
at android.os.Parcel.readParcelable(Parcel.java:2358)
at android.os.Parcel.readValue(Parcel.java:2264)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2614)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.BaseBundle.getString(BaseBundle.java:920)
at android.content.Intent.getStringExtra(Intent.java:6200)
at com.android.server.am.ActivityStackSupervisor.startActivityLocked(ActivityStackSupervisor.java:2752)
at com.android.server.am.ActivityStackSupervisor.startActivityMayWait(ActivityStackSupervisor.java:2209)
at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:6401)
at com.android.server.am.ActivityManagerService.startActivity(ActivityManagerService.java:6173)
at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:170)
at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:4028)
at android.os.Binder.execTransact(Binder.java:453)
Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
Based on research into that error, the previous version I attempt to install over was built a few days ago using Android Studio with no major changes or updates in between, and is only using V1 signatures as was the case with the previous build. Changing back to the incorrect way, setting the code to 9 and incrementing only the version name allows the update to install correctly.
Here's what I think happening.
If we have a test class thus:
public class MyClass {
public static void main(String args[]) {
int anInt = '1';
System.out.println("anInt is = " + anInt);
}
}
It runs with result `anInt is = 49`
change '1' to '9'
It runs with result `anInt is = 57`
change '9' to "10"
It fails (like your cast error):
/MyClass.java:3: error: incompatible types: String cannot be converted to int
int anInt = "10";
^
1 error
So if you use your bug fixed code with a
versionCode
of 58 it should work.
here's a snippet
so you can try it yourself in javascript
:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html><head><script>
$(function() {
var anInt = ('1').charCodeAt(0);
console.info("anInt is = " + anInt);
});
</script></head><body>
function();
</body></html>