Ich habe eine Bibliothek, die ich in einem meiner Projekte verwenden muss, die leider ihren eigenen URLStreamHandler
registriert, um http-URLs
zu behandeln. Gibt es einen Weg, um eine Referenz auf Javas Standard http- und https-URLStreamHandlers
zu erhalten, damit ich einen von ihnen im Konstruktor von URL
spezifizieren kann, um eine Standard http-Verbindung zu öffnen, ohne das vom Bibliothek überschriebene Protokoll zu verwenden?
Antworten
Zu viele Anzeigen?Gefunden:
sun.net.www.protocol.http.Handler
Damit kann ich jetzt Folgendes tun:
URL url = new URL(null, "http://...", new sun.net.www.protocol.http.Handler());
HttpURLConnection cxn = (HttpURLConnection) url.openConnection();
Und ich erhalte eine normale Java HttpURLConnection und nicht die vom Framework bereitgestellte.
Aktualisierung:
Ich habe einen anderen, allgemeineren Ansatz entdeckt: Entfernen Sie die URLStreamHandlerFactory
des Frameworks!
Das ist etwas knifflig, da die URL-Klasse technisch gesehen nicht zulässt, dass Sie die Factory mehr als einmal einstellen oder sie mit einem offiziellen Funktionsaufruf löschen, aber mit etwas Reflektionsmagie können wir es trotzdem tun:
public static String unsetURLStreamHandlerFactory() {
try {
Field f = URL.class.getDeclaredField("factory");
f.setAccessible(true);
Object curFac = f.get(null);
f.set(null, null);
URL.setURLStreamHandlerFactory(null);
return curFac.getClass().getName();
} catch (Exception e) {
return null;
}
}
Diese Methode greift auf das statische Feld factory
in der URL-Klasse zu, macht es zugänglich, holt seinen aktuellen Wert und ändert ihn in null. Anschließend ruft sie URL.setStreamHandlerFactory(null) auf (was jetzt ohne Fehler abgeschlossen wird), um diese Einstellung "offiziell" zu machen, d.h. die Funktion eine Chance zu geben, eventuelle weitere Aufräumarbeiten durchzuführen, die sie tun möchte. Dann gibt sie den Klassenname der zuvor registrierten Factory zur Referenz zurück. Wenn etwas schiefgeht, wird die Ausnahme unterdrückt (ich weiß, keine gute Idee...) und null zurückgegeben.
Zur Referenz: Hier ist der relevante Quellcode für URL.java.
Anmerkung: Dieser Ansatz könnte sogar riskanter sein als die Verwendung der internen sun-Klassen (was die Portabilität betrifft), da er auf der spezifischen internen Struktur der URL-Klasse basiert (nämlich dem Vorhandensein und der genauen Funktionalität des factory
-Feldes), aber er hat den Vorteil, dass ich nicht durch meinen gesamten Code gehen muss, um alle URL-Konstruktoren zu finden und den Handler-Parameter hinzuzufügen... Außerdem könnte er einige Funktionen des Frameworks beeinträchtigen, die auf ihren registrierten Handlern beruhen. Glücklicherweise sind weder das Problem der Portabilität noch das teilweise defekte Bibliotheksfunktionalität in meinem Fall relevant.
Aktualisierung: #2
Da wir die Reflektion verwenden: Hier ist wahrscheinlich der sicherste Weg, eine Referenz auf den Standard-Handler zu erhalten:
public static URLStreamHandler getURLStreamHandler(String protocol) {
try {
Method method = URL.class.getDeclaredMethod("getURLStreamHandler", String.class);
method.setAccessible(true);
return (URLStreamHandler) method.invoke(null, protocol);
} catch (Exception e) {
return null;
}
}
den Sie einfach so aufrufen:
URLStreamHandler hander = getURLStreamHandler("http");
Anmerkung: Dieser Aufruf muss vor der Registrierung der URLStreamHandlerFactory
des Frameworks stattfinden, da Sie sonst eine Referenz auf ihren Handler erhalten.
Warum halte ich dies für den sichersten Ansatz? Weil URL.getURLStreamHandler(...)
keine vollständig private Methode ist, sondern nur paketprivat. Das Ändern ihrer Signatur könnte also anderen Code im selben Paket brechen. Außerdem lässt ihr Name nicht viel Spielraum dafür, etwas anderes als das zurückzugeben, wonach wir suchen. Daher würde ich erwarten, dass es unwahrscheinlich ist (obwohl immer noch nicht unmöglich), dass eine andere/zukünftige Implementierung der URL
-Klasse mit den hier gemachten Annahmen unvereinbar wäre.
Hier ist Markus A.Lösung für Android neu geschrieben:
public static URLStreamHandler getURLStreamHandler(String protocolIdentifier) {
try {
URL url = new URL(protocolIdentifier);
Field handlerField = URL.class.getDeclaredField("handler");
handlerField.setAccessible(true);
return (URLStreamHandler)handlerField.get(url);
} catch (Exception e) {
return null;
}
}
Die Methode getURLStreamHandler existiert nicht in Android, daher muss es anders gemacht werden. Der protocolIdentifier ist der Protokollname plus den Doppelpunkt. Sie müssen einen Wert übergeben, der verwendet werden kann, um eine URL-Instanz für den gewünschten Protokoll-URLStreamHandler zu instanziieren. Wenn Sie den Standard-"jar:"-Protokollhandler möchten, müssen Sie etwas wie dies eingeben: "jar:file:!/". Das "file" könnte durch "http" oder "https" ersetzt werden, da sie alle die gleiche Handler-Instanz erhalten.
Die Standard-Handler, die von Android unterstützt werden, sind http, https, file, jar, ftp.
Sie können Ihren eigenen URLStreamHandlerFactory
mit URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory());
registrieren
public class URLStreamHandlerFactory implements java.net.URLStreamHandlerFactory {
public URLStreamHandlerFactory() {}
public URLStreamHandler createURLStreamHandler(String protocol) {
if(protocol.equals("http")) {
return new sun.net.www.protocol.http.Handler();
} else if(protocol.equals("https")) {
return new sun.net.www.protocol.https.Handler();
}
return null;
}
}
damit Sie den Standard-Handler verwenden können
EDIT: Gefunden diesen Code