I've come across an unexpected error caused by calling URL.setURLStreamHandlerFactory(factory);
in an Android application that is being updated.
public class ApplicationRoot extends Application {
static {
/* Add application support for custom URI protocols. */
final URLStreamHandlerFactory factory = new URLStreamHandlerFactory() {
@Override
public URLStreamHandler createURLStreamHandler(final String protocol) {
if (ExternalProtocol.PROTOCOL.equals(protocol)) {
return new ExternalProtocol();
}
if (ArchiveProtocol.PROTOCOL.equals(protocol)) {
return new ArchiveProtocol();
}
return null;
}
};
URL.setURLStreamHandlerFactory(factory);
}
}
Intro:
Here is my situation: I'm maintaining a non-market application used in an enterprise fashion. My business sells tablets with pre-installed applications that are developed and maintained by the business. These pre-installed applications are not part of the ROM; they are installed as typical Unknown Source applications. We do not perform updates through the Play Store or any other market. Rather, application updates are controlled by a custom Update Manager application, which communicates directly with our servers to perform OTA updates.
Problem:
This Update Manager application, which I am maintaining, occasionally needs to update itself. Immediately after the application updates itself, it restarts by way of the android.intent.action.PACKAGE_REPLACED
broadcast, which I register for in the AndroidManifest. However, upon restart of the application immediately after the update, I occasionally receive this Error
java.lang.Error: Factory already set
at java.net.URL.setURLStreamHandlerFactory(URL.java:112)
at com.xxx.xxx.ApplicationRoot.<clinit>(ApplicationRoot.java:37)
at java.lang.Class.newInstanceImpl(Native Method)
at java.lang.Class.newInstance(Class.java:1208)
at android.app.Instrumentation.newApplication(Instrumentation.java:996)
at android.app.Instrumentation.newApplication(Instrumentation.java:981)
at android.app.LoadedApk.makeApplication(LoadedApk.java:511)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:2625)
at android.app.ActivityThread.access$1800(ActivityThread.java:172)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1384)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:146)
at android.app.ActivityThread.main(ActivityThread.java:5653)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
at dalvik.system.NativeStart.main(Native Method)
Note that the majority of the time, the application restarts properly. However, every once in a while, I get the above error. I'm perplexed because the only place I call setURLStreamHandlerFactory
is here, and it is done in a static
block, which I presume - although correct me if I'm wrong - is only called once, when the ApplicationRoot
class if first loaded. However, it would seem that it is getting called twice, resulting in the above error.
Question:
What in the blazing sams is going on? My only guess at this point is that the VM/process for the updated application is the same as the previously installed application that is being updated, so when the static
block for the new ApplicationRoot
gets called, the URLStreamHandlerFactory
set by the old ApplicationRoot
is still "active". Is this possible? How can I avoid this situation? Seeing that it doesn't always happen, it seems to be a race condition of some sort; maybe within Android's APK installation routine? Thanks,
Edit:
Additional code as requested. Here is the manifest portion dealing with the Broadcast
<receiver android:name=".OnSelfUpdate" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
And the BroadcastReceiver
itself
public class OnSelfUpdate extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
/* Get the application(s) updated. */
final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
final PackageManager packageManager = context.getPackageManager();
final String[] packages = packageManager.getPackagesForUid(uid);
if (packages != null) {
final String thisPackage = context.getPackageName();
for (final String pkg : packages) {
/* Check to see if this application was updated. */
if (pkg.equals(thisPackage)) {
final Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
break;
}
}
}
}
}