2015-05-15 34 views
15

我遇到了在正在更新的Android應用程序中調用URL.setURLStreamHandlerFactory(factory);導致的意外錯誤。setURLStreamHandlerFactory和「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); 
    } 

} 

簡介:

這裏是我的情況:我保持企業的方式使用非市場應用。我的公司出售帶有由企業開發和維護的預安裝應用程序的平板電腦。這些預裝應用程序不是ROM的一部分;它們被安裝爲典型的未知來源應用程序。我們不會通過Play商店或任何其他市場執行更新。相反,應用程序更新由自定義更新管理器應用程序控制,該應用程序直接與我們的服務器通信以執行OTA更新。

問題:

更新管理器應用程序,我維護,偶爾需要更新自己。在應用程序自身更新後,立即通過我在AndroidManifest中註冊的android.intent.action.PACKAGE_REPLACED廣播重新開始。然而,在應用程序的重啓更新後,我偶爾收到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) 

注意,大部分時間,應用程序重新啓動正常。但是,每過一段時間,我都會遇到上述錯誤。我很困惑,因爲只有地方我打電話setURLStreamHandlerFactory在這裏,它是在static塊,我認爲 - 雖然糾正我,如果我錯了 - 只調用一次,當ApplicationRoot類如果第一次加載。但是,它似乎被稱爲兩次,導致上述錯誤。

問:

什麼在熾熱的SAMS是怎麼回事?我在這一點上唯一的猜測是,對於更新應用VM /過程是一樣的以前安裝正在更新應用程序,所以,當爲ApplicationRootstatic塊被調用時,URLStreamHandlerFactory集由ApplicationRoot仍然「活躍」。這可能嗎?我怎樣才能避免這種情況?看到它並不總是發生,似乎是某種競爭條件;也許在Android的APK安裝例程中?謝謝,

編輯:

附加代碼的要求。這裏是清單部分涉及廣播

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

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

} 
+2

在一個正切的說明中,決定拋出'Error'而不是'IllegalStateException'的人需要被打耳光。 – chrylis

+0

可以發佈用於發送廣播和清單屬性的代碼以捕獲它嗎? – Simas

+0

@Simas Added。廣播由OS發送。 – pathfinderelite

回答

1

AFAIK你/不要重新啓動JVM。此外,正如你已經發現的那樣,你不能在單個應用程序的JVM中設置兩次URLStreamHandlerFactory

您的程序應該嘗試設置工廠,只有當它是不是:

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

如果你的應用程序的更新還包括更新工廠,你可以嘗試killing the process your app resides in但我不這是個好主意這樣做甚至更糟糕 - 甚至可能無法工作。

+0

* An Error是Throwable的一個子類,它指示合理應用程序不應該嘗試捕獲的嚴重問題*從[doc](http://docs.oracle.com/javase/7/docs/api/java/lang/的error.html)。由於JVM可能保持異常狀態,因此我很不舒服地捕捉到'Error'。然後再次,它不像一個崩潰的應用程序不正常:)。無論如何,謝謝,但這並沒有真正回答我爲什麼會發生這樣的問題。 – pathfinderelite

+0

@pathfinderelite你可以隨時檢查拋出的錯誤是否是你期望拋出的錯誤,否則重新拋出它,或者你可以通過反射來獲取靜態變量,並檢查它是否爲空。 – Simas

+0

不幸的是,被動地忽視這種情況有另一個潛在的更重要的問題。如果更新的應用程序以不同於先前版本的方式實現URLStreamHandlerFactory,則新工廠將不會被設置。舊工廠仍然在原地。這也引發了一個問題,那就是舊工廠使用「ExternalProtocol」和「ArchiveProtocol」類嗎?那些來自以前的版本或更新的版本? – pathfinderelite

4

靜態塊在加載類時執行 - 如果類由於某種原因被重新加載(例如,當它被更新時),它將被再次執行。

在你的情況下,這意味着你設置的上一次加載的URLStreamHandlerFactory將保留。

這不是一個真正的問題,除非你更新了URLStreamHandlerFactory

有固定的這兩種方法:

  1. 趕上Error並繼續你的快樂的方式,忽略了你還在使用舊廠房的事實。

  2. 實現一個非常簡單的包裝,代表另一個URLStreamHandlerFactory,你可以替換,你不會有改變。你會在這裏遇到同樣的問題與包裝,但所以你需要趕上那一個Error或結合選項3.

  3. 跟蹤是否已經安裝處理程序使用系統屬性。

代碼:

public static void maybeInstall(URLStreamHandlerFactory factory) { 
    if(System.getProperty("com.xxx.streamHandlerFactoryInstalled") == null) { 
     URL.setURLStreamHandlerFactory(factory); 
     System.setProperty("com.xxx.streamHandlerFactoryInstalled", "true"); 
    } 
} 
  • 強制替換使用反射。我完全不知道爲什麼你只能設置URLStreamHandlerFactory一次 - 對我來說TBH沒什麼意義。
  • 代碼:

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

    字段名稱是factory Oracle的JRE,可能會在Android上的不同。

    +0

    謝謝,#2看起來很有希望。 – pathfinderelite