2015-05-15 8 views
15

Wystąpił nieoczekiwany błąd spowodowany wywołaniem URL.setURLStreamHandlerFactory(factory); w zaktualizowanej aplikacji systemu Android.setURLStreamHandlerFactory i "java.lang.Error: Factory already set"

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:

Oto moja sytuacja: Mam utrzymanie aplikacji non-rynkowych stosowanych w modzie przedsiębiorstw. Moja firma sprzedaje tablety z wstępnie zainstalowanymi aplikacjami opracowanymi i utrzymywanymi przez firmę. Te wstępnie zainstalowane aplikacje nie są częścią ROM; są instalowane jako typowe aplikacje Nieznane źródło. Nie wykonujemy aktualizacji za pośrednictwem Sklepu Play ani żadnego innego rynku. Zamiast tego aktualizacje aplikacji są kontrolowane przez niestandardową aplikację Update Manager, która komunikuje się bezpośrednio z naszymi serwerami w celu wykonywania aktualizacji OTA.

Problem:

Ten Aktualizacja aplikacji Menedżer, której mam utrzymania, od czasu do czasu musi się zaktualizować. Natychmiast po aktualizacji aplikacja restartuje się za pomocą transmisji android.intent.action.PACKAGE_REPLACED, którą rejestruję w AndroidManifest. Jednak po ponownym uruchomieniu aplikacji natychmiast po aktualizacji, ja sporadycznie otrzymać ten 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) 

Należy pamiętać, że większość czasu, aplikacja uruchomi się ponownie prawidłowo. Jednak co jakiś czas pojawia się powyższy błąd. Jestem zakłopotany, ponieważ tutaj znajduje się i jest to robione w bloku , który, jak sądzę, - chociaż poprawi mnie, jeśli się mylę - jest wywoływany tylko raz, gdy klasa ApplicationRoot jest ładowana po raz pierwszy . Jednak wydaje się, że jest on nazywany dwukrotnie, powodując powyższy błąd.

Pytanie:

Co w płonących Sams się dzieje? My tylko domyślać, w tym momencie jest to, że VM/Sposób zaktualizowanej aplikacji jest taka sama jak wcześniej zainstalowany aplikację, która jest aktualizowana, więc gdy blok static dla nowegoApplicationRoot jest wywoływana, zestaw URLStreamHandlerFactory przez staryApplicationRoot jest nadal "aktywny". czy to możliwe? Jak mogę uniknąć tej sytuacji? Widząc, że nie zawsze tak się dzieje, wydaje się, że jest to jakiś rodzaj wyścigu; może w ramach procedury instalacji APK Androida? Dzięki,

Edit:

Kod dodatkowy żądanie.Oto manifest część czynienia z Broadcast

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

A BroadcastReceiver sama

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

} 
+2

W notatce stycznej, ktokolwiek zdecydował się rzucić "Błąd" zamiast "IllegalStateException", musi zostać spoliczkowany. – chrylis

+0

Czy można wysłać kod wysyłania atrybutów emisji i manifestu w celu ich przechwycenia? – Simas

+0

@Simas Dodano. Transmisja jest wysyłana przez system operacyjny. – pathfinderelite

Odpowiedz

1

AFAIK można/nie powinno ponownie uruchomić JVM. Ponadto, jak już się dowiedziałeś, nie możesz dwukrotnie ustawić URLStreamHandlerFactory w maszynie JVM dla pojedynczej aplikacji.

Aplikacja powinna spróbować ustawić zakład tylko wtedy, gdy nie jest:

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

Jeśli aktualizacje aplikacji obejmują także aktualizowanie fabryki, można spróbować killing the process your app resides in ale nie jest to dobry pomysł, aby Zrób tak, jeszcze gorzej - może nawet nie działać.

+0

* Błąd jest podklasą Throwable, która wskazuje na poważne problemy, których uzasadniona aplikacja nie powinna próbować wykryć * From [doc] (http://docs.oracle.com/javase/7/docs/api/java/lang/ Error.html).Czuję się niekomfortowo łapiąc "Błąd", ponieważ JVM może pozostać w nienormalnym stanie. Z drugiej strony, nie jest tak nienormalny jak aplikacja po awarii :). W każdym razie, dziękuję, ale to tak naprawdę nie odpowiada na moje pytanie, dlaczego tak się dzieje. – pathfinderelite

+0

@pathfinderelite Zawsze możesz sprawdzić, czy zgłoszony błąd jest tym, czego oczekiwałeś, że inaczej wrzucisz go ponownie, lub zamiast tego uzyskasz zmienną statyczną poprzez odbicie i sprawdzisz, czy jest ona pusta. – Simas

+0

Niestety, bierne ignorowanie sytuacji ma inny, potencjalnie ważniejszy problem. Jeśli zaktualizowana aplikacja implementuje obiekt URLStreamHandlerFactory inaczej niż w poprzedniej wersji, nowa fabryka nie zostanie ustawiona. Stara fabryka nadal byłaby na miejscu. To również nasuwa pytanie, które klasy "ExternalProtocol" i "ArchiveProtocol" będą używać w starej fabryce? Te z poprzedniej wersji lub zaktualizowanej wersji? – pathfinderelite

4

Bloki statyczne są wykonywane po załadowaniu klasy - jeśli klasa zostanie przeładowana z jakiegoś powodu (na przykład po aktualizacji), zostanie wykonana ponownie.

W twoim przypadku oznacza to, że ustawiony przez ciebie poprzedni czas pozostanie.

To naprawdę nie jest problem, chyba że zaktualizowałeś URLStreamHandlerFactory.

Istnieją dwa sposoby mocowania to:

  1. złapać Error i dalej na wesoły sposób, ignorując fakt, że nadal używasz starej fabryki.

  2. Wdrożenie bardzo prosty wrapper że delegaci do innego URLStreamHandlerFactory że może zastąpić i że nie będzie trzeba zmieniać. Spowoduje to uruchomienie tego samego problemu z opakowaniem, więc musisz złapać numer Error lub połączyć go z opcją 3.

  3. Śledź, czy już zainstalowałeś handler'a przy pomocy właściwość systemu.

Kod:

public static void maybeInstall(URLStreamHandlerFactory factory) { 
    if(System.getProperty("com.xxx.streamHandlerFactoryInstalled") == null) { 
     URL.setURLStreamHandlerFactory(factory); 
     System.setProperty("com.xxx.streamHandlerFactoryInstalled", "true"); 
    } 
} 
  1. zastępczej siły przy użyciu odbicia. Nie mam pojęcia, dlaczego można ustawić tylko URLStreamHandlerFactory raz - nie ma to dla mnie większego znaczenia TBH.

Kod:

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

Nazwa pola jest factory na Oracle JRE, mogą być różne na Androida.

+0

Dzięki, # 2 wydaje się obiecująca. – pathfinderelite

Powiązane problemy