setURLStreamHandlerFactory y "java.lang.Error: La fábrica ya está configurada"

Me he encontrado con un error inesperado causado por la llamada URL.setURLStreamHandlerFactory(factory); en una aplicación de Android que se está actualizando.

 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:

Aquí está mi situación: estoy manteniendo una aplicación no comercial utilizada en una empresa de moda. Mi negocio vende tabletas con aplicaciones preinstaladas que son desarrolladas y mantenidas por la empresa. Estas aplicaciones preinstaladas no forman parte de la ROM; se instalan como aplicaciones de fuente desconocida típicas. No realizamos actualizaciones a través de Play Store ni en ningún otro mercado. Por el contrario, las actualizaciones de las aplicaciones son controladas por una aplicación personalizada de Update Manager , que se comunica directamente con nuestros servidores para realizar actualizaciones OTA.

Problema:

Esta aplicación de Gestor de actualizaciones , que estoy manteniendo, de vez en cuando necesita actualizarse. Inmediatamente después de que la aplicación se actualice, se reinicia por medio de la emisión android.intent.action.PACKAGE_REPLACED , en la que me registro en el AndroidManifest. Sin embargo, al reiniciar la aplicación inmediatamente después de la actualización, de vez en cuando recibir este 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) 

Tenga en cuenta que la mayoría de las veces, la aplicación se reinicia correctamente. Sin embargo, de vez en cuando, obtengo el error anterior. Estoy perplejo porque el único lugar al que llamo setURLStreamHandlerFactory está aquí, y se hace en un bloque static , que presumo -aunque me corrija si estoy equivocado- sólo se llama una vez, cuando la clase ApplicationRoot carga primero. Sin embargo, parece que se está llamando dos veces , lo que resulta en el error anterior.

Pregunta:

¿Qué es lo que está ocurriendo en los llamativos sams ? Mi única suposición en este punto es que el proceso VM / para la aplicación actualizada es la misma que la aplicación instalada anteriormente que se está actualizando, por lo que cuando el bloque static para el nuevo ApplicationRoot se llama, el URLStreamHandlerFactory establecido por el antiguo ApplicationRoot sigue siendo "activo". es posible? ¿Cómo puedo evitar esta situación? Al ver que no siempre sucede, parece ser una condición de carrera de algún tipo; tal vez dentro de la rutina de instalación de APK de Android? Gracias,

Editar:

Código adicional solicitado. Aquí está la parte manifiesta que trata de la transmisión

 <receiver android:name=".OnSelfUpdate" > <intent-filter> <action android:name="android.intent.action.PACKAGE_REPLACED" /> <data android:scheme="package" /> </intent-filter> </receiver> 

Y el propio BroadcastReceiver

 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; } } } } } 

Los bloques estáticos se ejecutan cuando se carga la clase – si la clase se vuelve a cargar por algún motivo (por ejemplo, cuando se ha actualizado) se ejecutará de nuevo.

En su caso, significa que se URLStreamHandlerFactory el URLStreamHandlerFactory que estableció el tiempo anterior al que se cargó.

Esto no es realmente un problema a menos que haya actualizado el URLStreamHandlerFactory .

Hay dos maneras de arreglar esto:

  1. Coja el Error y continúe en su manera alegre, ignorando el hecho de que usted todavía está utilizando la fábrica vieja.

  2. Implemente un contenedor muy simple que delega a otro URLStreamHandlerFactory que puede reemplazar y que no tendrá que cambiar. Usted funcionará en el mismo problema aquí con el envoltorio sin embargo así que usted necesita coger el Error en ése o combinarlo con la opción 3.

  3. Compruebe si ya ha instalado el controlador utilizando una propiedad del sistema.

Código:

 public static void maybeInstall(URLStreamHandlerFactory factory) { if(System.getProperty("com.xxx.streamHandlerFactoryInstalled") == null) { URL.setURLStreamHandlerFactory(factory); System.setProperty("com.xxx.streamHandlerFactoryInstalled", "true"); } } 
  1. Reemplazo de fuerza mediante la reflexión. No tengo absolutamente ninguna idea de por qué sólo se puede establecer la URLStreamHandlerFactory una vez – no tiene mucho sentido para mí TBH.

Código:

 public static void forcefullyInstall(URLStreamHandlerFactory factory) { try { // Try doing it the normal way URL.setURLStreamHandlerFactory(factory); } catch (final Error e) { // Force it via reflection try { final Field factoryField = URL.class.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(null, factory); } catch (NoSuchFieldException | IllegalAccessException e1) { throw new Error("Could not access factory field on URL class: {}", e); } } } 

El nombre del campo es de factory en JRE de Oracle, puede ser diferente en Android.

AFAIK puede / no debe reiniciar la JVM. Además, como ya lo has descubierto, no puedes establecer URLStreamHandlerFactory dos veces en una JVM para una sola aplicación.

Su aplicación debe intentar establecer la fábrica sólo cuando no está:

 try { URL.setURLStreamHandlerFactory(factory); } catch (Error e) { e.printStackTrace(); } 

Si las actualizaciones de su aplicación también incluyen la actualización de la fábrica, podría intentar matar el proceso en el que reside su aplicación, pero no es una buena idea hacerlo, peor aún, puede que ni siquiera funcione.

  • Conexión de TextWatcher con un adaptador personalizado para filtrar texto a través de EditText
  • URL de caracteres ilegales
  • ¿Valores de diálogo y edittext de Android?
  • ¿Qué significa COLLATE LOCALIZED ASC?
  • ¿Es posible iniciar una actividad en un apk diferente usando startActivity en Android usando el nombre de actividad o similar?
  • Android - problemas de SSL en el emulador de estudio de Android, funciona bien en el teléfono
  • ¿Cómo puedo convertir una parte del archivo de código fuente de Java a Kotlin?
  • Uso de la gestión de excepciones globales con "setUncaughtExceptionHandler" y "Toast"
  • Conversión de OpenCV C ++ a Java para problemas de detección de formas
  • Java.lang.RuntimeException: error: 0D0680A8: rutinas de codificación asn1: ASN1_CHECK_TLEN: etiqueta equivocada
  • Acceso a WebView desde otra función en la clase MainActivity
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.