Entwickler-Anleitung Teil 2: So programmiert Ihr einen RSS-Reader
Dies ist der zweite Teil in unserer Serie wie man in Java für Android programmiert (zum ersten Teil). Nachdem Ihr in der Einführung eine erste App geschrieben habt, werde ich Euch heute zeigen, wie Ihr den RSS-Feed von AndroidPIT anzeigen könnt. Dadurch kann ich Euch verschiedene Grundlagen und Konzepte erläutern, die Ihr für die Entwicklung für Android benötigt.
Wenn Ihr immer noch das Hallo-Welt-Projekt aus dem ersten Teil habt, könnt Ihr auch damit weiter arbeiten, ansonsten legt Ihr im Android Developer Studio einfach ein neues Projekt an. Inzwischen solltet Ihr das alleine hinbekommen. Falls nicht, wäre jetzt ein guter Zeitpunkt, meine erste Anleitung noch einmal zu lesen.
Rich Site Summary (RSS)
Zuallererst: Was ist ein RSS-Feed? Viele von euch werden täglich RSS-Feeds lesen in einer Reader-App auf Eurem Smartphone oder Tablet. Zum Programmieren einer App, die einen RSS-Feed anzeigen kann, solltet Ihr aber ein wenig über die Hintergründe Bescheid wissen. Wikipedia gibt Euch einen ganz guten Überblick über RSS.
Der RSS-Feed von AndroidPIT ist unter dieser URL zu finden: https://www.nextpit.de/feed/main.xml
Vereinfacht gesagt ist RSS eine XML-Datei. XML ist prinzipiell lesbar, gleichzeitig aber auch für die Maschinenlesbarkeit ausgelegt. Unser erstes Ziel ist es, eine App zu schreiben, welche die XML-Datei des AndroidPIT-Feeds in TextView anzeigt, so wie wir auch in der "Hallo AndroidPIT" App vorgegangen sind.
Darstellen des RSS-Feeds
Als nächstes zeige ich Euch, wie Ihr die XML-Datei auf den Bildschirm Eures Smartphones bringt. Eure erste App im vorherigen Teil dieser Serie gab "Hallo Welt!" aus. Wir werden das durch den RSS-Feed ersetzen.
Unter Android wird das Layout normalerweise über XML definiert. Wenn Ihr also ein wenig Zeit in das Lernen von XML investiert, erwerbt Ihr Wissen, dass auch für die Entwicklung für Android wichtig und wertvoll ist. XML ist definitiv ein Format, das Ihr kennen solltet.
Das Layout ist im res/layout Ordner abgelegt. Wenn Ihr fragment_main.xml Eures Projektes öffnet, seht Ihr die XML-Layout-Datei zusammen mit einer Vorschau.
Wir wollen "Hallo Welt!" mit unserem RSS-Feed ersetzen. Dazu müssen wir der TextView-Anweisung zur Darstellung von Text einen Identifikator geben. Ihr werdet gleich sehen, wieso das nötig ist. Fügt eine android:id Eigenschaft zur TextView-Anweisung hinzu.
<TextView android:id="@+id/rss_feed" android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" />
Nachdem wir TextView mit einer id versehen haben, müssen wir sie finden und ein Feld zu PlaceholderFragment hinzufügen mit einer Referenz auf die TextView-Anweisung, damit wir sie später ändern können. Dazu verwenden wir die Methode findViewById. Eure onCreateView Methode sollte so aussehen:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); mRssFeed = (TextView) rootView.findViewById(R.id.rss_feed); return rootView; }
Um den RSS-Feed beim Start der App abzurufen müssen wir die folgende onStart Methode hinzufügen:
@Override public void onStart() { super.onStart(); InputStream in = null; try { URL url = new URL("https://www.nextpit.com/feed/main.xml"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; for (int count; (count = in.read(buffer)) != -1; ) { out.write(buffer, 0, count); } byte[] response = out.toByteArray(); String rssFeed = new String(response, "UTF-8"); mRssFeed.setText(rssFeed); } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } }
Wenn Ihr diesen Quellcode nicht versteht, keine Sorge, fügt Ihn einfach mit Copy-Paste ein. Zum Verständnis könnt Ihr Java lernen, während Ihr das Entwickeln für Android lernt.
Wenn Ihr die App jetzt ausführt, sollte das Kompilieren zwar klappen, sie wird beim Ausführen aber abstürzen.
Bei der App-Entwicklung ist es nötig, dass Ihr so früh wie möglich lernt, wie mit Abstürzen umzugehen ist. Gerade habt ihr Euren ersten Absturz selbst hervorgerufen. Beim Starten der Anwendung wurde gleichzeitig Logcat geöffnet. In Logcat protokolliert Android alle Nachrichten und Fehlermeldungen der Anwendung, das entsprechende Fenster öffnet sich beim ersten Start des Projektes. Folgende Fehlermeldung solltet Ihr vorfinden:
com.rockylabs.androidpitrss E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.rockylabs.androidpitrss, PID: 14367 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.rockylabs.androidpitrss/com.rockylabs.androidpitrss.MainActivity}: android.os.NetworkOnMainThreadException
Was ist passiert? Eure Anwendung hat versucht, im Haupt-Thread auf das Internet zuzugreifen. Dauert diese Anfrage zu lange, reagiert die Anwendung nicht mehr und eine Fehlermeldung wird ausgegeben. Um das zu vermeiden, wurde Android von Google dahingehend geändert, dass die Anwendung abstürzt. In älteren Versionen war das noch nicht so, weshalb einige Entwickler vom Hauptstrang ihrer Anwendungen auf das Netzwerk zugriffen.
Bei Android müsst Ihr Euch mit der Welt der asynchronen Arbeitsabläufe vertraut machen. Das bedeutet, dass Ihr eine Anfrage an das System stellt, die dann im Hintergrund ausgeführt wird und eine Benachrichtigung zurückgibt, sobald die Aufgabe abgeschlossen wurde. Wenn Ihr noch nicht mit Threads (Ausführungssträngen) gearbeitet habt, könnt Ihr es nach und nach lernen.
Der Standard ist, alle Aufgaben im Hauptstrang (Main Thread) der Anwendung auszuführen. Dieser Thread wird auch User Interface Thread genannt, kurz UI-Thread. Wenn Ihr von diesem Strang auf das Internet zugreift und eine Serveranfrage schickt, die zu lange dauert, kann das Interface in dieser Zeit nichts auf dem Bildschirm ausgeben. In der Folge bekommt Ihr von Android die Fehlermeldung, dass die Anwendung nicht reagiert. Diese Warnung wird auch ANR genannt, für Application Not Responding. Eine solche Meldung sollte auf jeden Fall vermieden werden aus Gründen der Benutzerfreundlichkeit.
Android stellt einen sehr einfachen Weg zur Handhabung dieses Problems zur Verfügung in Form der Klasse AsyncTask. Dadurch könnt Ihr eine Aufgabe in einem Hintergrund-Thread ausführen und das Ergebnis an den UI-Thread zurückliefern.
In unserer App wollen wir also eine Aufgabe mit AsyncTask im Hintergrund erledigen. Im vorherigen Schritt haben wir Quellcode in die onStart Methode eingefügt. Jetzt müssen wir Netzwerk und User Interface trennen, weshalb wir eine Methode getAndroidPitRssFeed anlegen. Dadurch wird es leichter, Quellcode mehrmals zu verwenden anstatt immer mit Copy-Paste zu arbeiten. Die getAndroidPitRssFeed Methode sieht so aus:
private class GetAndroidPitRssFeedTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... voids) { String result = ""; try { result = getAndroidPitRssFeed(); } catch (IOException e) { e.printStackTrace(); } return result; } @Override protected void onPostExecute(String rssFeed) { mRssFeed.setText(rssFeed); } }
Ein erneutes Ausführen der App wird wieder einen Absturz produzieren, aber dieses Mal wegen einer fehlenden Berechtigung: Ohne gesetzte Berechtigung für den Zugriff auf das Internet können wir den RSS-Feed nicht abrufen.
In Logcat findet Ihr folgenden Eintrag:
Caused by: java.lang.SecurityException: Permission denied (missing INTERNET permission?)
Zum Setzen der Internet-Berechtigung öffnet Ihr die Datei AndroidManifest.xml - eine weitere XML-Datei. Dort solltet Ihr diese Zeilen sehen:
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="19" />
Unterhalb fügen wir folgendes ein:
<uses-permission android:name="android.permission.INTERNET" />
Wenn Ihr jetzt die Anwendung ausführt, solltet Ihr "Hallo Welt" sehen können und nach einigen Sekunden den XML-Code des RSS-Feeds. Wie Euch bestimmt auffällt, dauert das Laden eine gewisse Zeit. Der gesamte Feed muss von der AndroidPIT-Website geladen werden. Abhänging von Eurer Verbindung kann das schon einen Moment in Anspruch nehmen. Später zeige ich Euch, wie wir die Benutzerfreundlichkeit weiter verbessern können, indem wir eine Ladeanzeige einbauen.
So weit, so gut. Das ist der Moment, an dem Ihr euch auf die Schulter klopft, einen Kaffee holt oder ein KitKat auspackt. Ihr habt schon viel gelernt, und das erste Ziel erreicht: Der RSS-Feed von AndroidPIT wird in Eurer eigenen App auf Eurem Smartphone angezeigt!
Aber kaum jemand schaut sich die Matrix verschlüsselt an oder will einen Feed im XML-Format lesen, außerdem endet die Anzeige am Bildschirmrand und wir können nicht nach unten scrollen.
ScrollView
Die Lösung lautet ScrollView und ist als Komponente schon im Android SDK vorhanden:
<ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/rss_feed" android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </ScrollView>
Die Paddings aus RelativeLayout könnt Ihr auch nach TextView verschieben, was ein bisschen besser aussehen sollte. Im nächsten Schritt lernt Ihr, wie die XML-Datei ausgelesen wird, damit wir die Titel der Artikel extrahieren und in einer Liste darstellen können, so wie es ist allen Feed-Readern zu sehen ist.
XML Auslesen
Unser nächstes Ziel ist es, die Titel der einzelnen Artikel aus den <title> Tags auszulesen. Dazu müssen wir in den <rss> Tag, von da in den <channel> Tag, dann in den <item> Tag, um schließlich bei <title> zu landen. Der Einfachheit halber ignorieren wir all die anderen Tags. Nachdem wir uns durch die ganze XML-Datei durchgearbeitet haben, sollten wir eine Liste aller Titel haben, die wir übersichtlich darstellen können.
Um die Handhabung einer XML-Datei Zeichen für Zeichen zu erklären bedarf, es eines ganzen Buches, oder besser, einer ganzen Bibliothek. Da das Arbeiten mit XML so verbreitet ist, gibt es diese Bibliothek zum Glück schon: Jemand ist uns zuvorgekommen und nimmt uns die Arbeit der Analyse ab. Das auseinander Nehmen von XML-Dateien nennt man Parsing, die Software dazu ist ein Parser. Wir verwenden den XmlPullParser aus dem Android SDK.
Bei der Analyse gibt es folgende Stadien:
START DOCUMENT
Anfang des Dokuments - Der Parser hat noch nichts gelesen.
START_TAG
Der Parser ist beim Start-Tag.
TEXT
Der Parser ist mit Inhalt beschäftigt.
END_TAG
parser is on end tag
END_DOCUMENT
Das Dokument ist zu Ende, kein Parsing mehr möglich.
Zuerst müssen wir den Parser erstellen: XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xpp = factory.newPullParser();
Danach müssen wir dem Parser eine Quelle geben. Wir weisen ihm den Strang zu, den wir vom Server geladen haben und momentan über TextView auf dem Bildschirm darstellen.
Wie ich schon erwähnt habe: Wir wollen den RSS-Feed lesen, Channel-, Item- und Title-Tags öffnen, um die Titel der Artikel auszulesen. Den gesamten Rest ignorieren wir. Wir fügen also readRSS, readChannel, readItem und readTitle Methoden hinzu. Das Ergebnis wird eine Liste von Titeln sein.
Wie können wir das RSS-Tag auslesen? Wir müssen nur die Channel-Tags lesen und den Rest überspringen.
private List<String> readRss(XmlPullParser parser) throws XmlPullParserException, IOException { List<String> items = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "rss"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (name.equals("channel")) { items.addAll(readChannel(parser)); } else { skip(parser); } } return items; }
Jetzt müssen wir readChannel implementieren, um die Inhalte auszulesen, den Rest lassen wir beseite:
private List<String> readChannel(XmlPullParser parser) throws IOException, XmlPullParserException { List<String> items = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "channel"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (name.equals("item")) { items.add(readItem(parser)); } else { skip(parser); } } return items; }
readItem ist auch nötig:
private String readItem(XmlPullParser parser) throws XmlPullParserException, IOException { String result = null; parser.require(XmlPullParser.START_TAG, null, "item"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (name.equals("title")) { result = readTitle(parser); } else { skip(parser); } } return result; }
Fast geschafft, wir müssen nur noch mit readTitle den Titel parsen:
// Processes title tags in the feed. private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, null, "title"); String title = readText(parser); parser.require(XmlPullParser.END_TAG, null, "title"); return title; }
Und den Text im <title> Tag:
private String readText(XmlPullParser parser) throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT) { result = parser.getText(); parser.nextTag(); } return result; }
Mit dieser Methode wird der Rest übersprungen:
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { if (parser.getEventType() != XmlPullParser.START_TAG) { throw new IllegalStateException(); } int depth = 1; while (depth != 0) { switch (parser.next()) { case XmlPullParser.END_TAG: depth–; break; case XmlPullParser.START_TAG: depth++; break; } } }
Jetzt seid Ihr schon fast Profis, was das XML-Parsing betrifft! Im nächsten Schritt werden wir die Titel der Artikel in einer Liste ausgeben.
ListFragment
Im Android SDK gibt es eine Klasse ListFragment zur einfacheren Handhabung von Listen.
Da wir bereits eine Liste von Text-Strängen haben, die wir aus der XML-Datei extrahiert haben, können wir uns einer praktischen Standard-Funktion von Android bedienen und mit Adapter die Titel darstellen. Ein Adapter ist eine Art Brücke zwischen den Daten, die wir darstellen vollen, und den TextViews, welche die Daten darstellen. Beim Entwickeln für Android werden Euch die Adapter häufig begegnen, Ihr kommt um die Handhabung also nicht herum.
Mit einem Adapter können wir eine TextView Methode für jeden Titel aus unserer Liste erstellen. Ihr müsst PlaceholderFragment so verändern, dass ListFragment statt Fragment verwendet wird.
public static class PlaceholderFragment extends ListFragment {
Schließlich müssen wir noch AsyncTask anpassen, um unseren Parser zu verwenden:
@Override protected List<String> doInBackground(Void... voids) { List<String> result = null; try { String feed = getAndroidPitRssFeed(); result = parse(feed); } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return result; } @Override protected void onPostExecute(List<String> rssFeed) { setListAdapter(new ArrayAdapter<>( getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, rssFeed)); }
Wenn Ihr Eure App jetzt ausführt, solltet Ihr zuerst eine Lade-Anzeige sehen, während der RSS-Feed vom Server heruntergeladen wird, danach wird eine Liste mit allen Titeln aus dem Feed dargestellt.
Zusammenfassung
Raucht Euch der Kopf? Ich weiß, dass diese Anleitung lang und umfangreich ist und ein großer Schritt im Vergleich zu unserer ersten Übung. Aber wenn Ihr Euch durch alles durchgekämpft habt, könnt Ihr erstens mächtig stolz auf Euch sein und habt zweitens eine Menge gelernt. Selbst wenn Ihr nicht jedes kleinste Detail verstanden habt, so wird Euer Verständnis von Android mit der Zeit wachsen. Je mehr Ihr euch mit der Entwicklung für Android beschäftigt, desto besser versteht Ihr das Betriebssystem als solches.
Die ganz Wagemutigen von Euch sollten sich jetzt hinsetzen und die nächste App schreiben, die eine Liste der Titel eines anderen RSS-Feeds anzeigt, zum Beispiel Eures eigenen Blogs (falls Ihr einen habt), oder der Nachrichtenquelle Eures Vertrauens.
Unser selbst programmierter Feed-Reader funktioniert, ist aber noch nicht sehr nützlich, da er außer den Artikelüberschriften nichts anzeigen kann. In unserer nächsten Übung wollen wir also den Funktionsumfang erweitern und die Einleitung der Artikel auslesen und darstellen, wenn auf den entsprechenden Titel getappt wird.
Was haltet Ihr davon? Welche Verbesserungen würdet Ihr an Eurer RSS-App vornehmen wollen? Lasst es mich in den Kommentaren wissen!
Hat jemand von euch den Quelltext-Download irgendwo rum liegen?
Hallo,
gerade auf dieses Tutorial gestoßen. Gibt es denn Quellcode dafür noch irgendwo? Der Link funktioniert nicht mehr :(
Hmm ich finde du solltest iwo erwähnen das man sich parallel deinen quellcode anschauen soll...wenn man stück für stückj geht sieht man in deinem text nicht das dein mRssFeed ne variable ist (die du nachher eh wieder auskommentierst). Deshalb haben die suer über mir immer den Fehler..sie haben eben die Variable nicht angeklickt.
WIe bekomme ich es jetzt hin das es den feed öffnet? Bisher sehe ich ja nur die Titel..oder hab ich was falsch gemacht?
Danke hat gut funktioniert, eine kleine Anmerkung: Ältere Android SDKs unterstützen Diamond Types (ArrayList<>) nicht, diese müssen dann einfach entfernt werden...
Ich habe schon einige Zeit PHP Programmierung auf dem Buckel, kenne mich also zumindest in den Basics aus. Leider nicht in Java und/oder OOP.
Und ich muss sagen: Der Arbeit die Ihr Euch gemacht habt gebührt sicherlich Respekt, aber mal im Ernst: Wirklich durchsteigen kann man da nicht! Gerade zu Anfang des Artikels wird mit Quelltext durch die Gegend geworfen ohne wirklich zu erklären wo (welches File) was hin muss.
Ergebnis bei mir: Es hagelt "cannot find symbol class XXXXX"-Fehlermeldungen. Und auch nach mehrmaligem Lesen verstehe ich nicht, wo nun mein Fehler liegt, bzw. wo ich schrauben müsste um diesen zu beheben.
Meinem Verständnis nach sollte ein Tutorial natürlich an der Praxis sein (was das hier sicherlich ist!), aber auch erklären was wann und warum passiert! Genau das fehlt bei Euch hier und so ist es für Einsteiger auch mehr als schwierig Fehler zu beheben oder zu verstehen was passiert.
Sorry, aber ich bin mächtig enttäuscht :(
was muss in dem quellcode alles geändert werden um ein anderen rss einzufügen? beispielweise von fatzebook
noch mehr tutorials
Sind in Arbeit :-)
Bei mir werden einige Sachen als fehlend angemeckert... :-(
Quelltext für das Entwickler-Anleitung-Teil-2: http://www.androidpit.de/de/android/forum/thread/586718/Entwickler-Anleitung-Teil-2-So-programmiert-Ihr-einen-RSS-Reader
top :-)
Möglicherweise reichen meine Java-Kenntnisse zum Verstehen nicht, aber ich bin grad etwas verwirrt. Ihr schreibt über eine getAndroidPitRssFeed Methode, aber als Quellcode findet sich eine Klasse namens GetAndroidPitRssFeedTask und keine Methode. Und was sollen diese drei Punkte in den Parametern der Methode doInBackground?
Vielleicht liegt es an meinem niedrigen Kenntnisstand bezgl. Android, aber ich halte das Tutorial für extrem schlecht geschrieben.
Hallo Zusammen,
erst einmal vielen dank für deine tollen Beträge zur App Programmierung. Dazu habe ich dann auch direkt eine Anfängerfrage. Ich hänge an der Stelle an der ich folgenden Code in der MainActivity unter den PlaceholderFragment eingefügt habe.
mRssFeed = (TextView) rootView.findViewById(R.id.rss_feed);
Wenn ich jetzt den Code kompilieren möchte bekomme ich folgenden Fehler: error: cannot find symbol variable mRssFeed.
Kannst du mir sagen wo und wie (string?) ich die Variable deklarieren muss oder habe ich den Code falsch eingefügt?
Vielen Dank
Schöne Weihnachtsfeiertage und einen guten Rutsch ins neue Jahr
Netten Gruß
Thomas
Habe genau das gleiche Problem...
Danke für den Aufwand, ich werde es jetzt auch ausprobieren.
Gut geschrieben.
Vielleicht war gestern (bei mir) die Nacht zu lang, aber kann es wohl sein das der Artikel nicht erklärt, wo der "Ladebildschirm" herkommt?
Der Einschub "Abstürze/Logcat" war super(!), da könntest du aber vielleicht noch genauer beschreiben, wie man das liest/interpretiert, denn damit sind ja selbst viele "Entwickler" offenbar völlig überfordert, wie man in eurem Entwicklerforum regelmäßig sehen kann ;-)
werde ich auch mal ausprobieren :)
Gut geschrieben. Eventuell könnte man im nächsten oder Übernächsten Tutorial eine Notification Benachrichtigung einbauen, wenn neue Feeds da sind :)
Spitzen Artikel. weiter so. :-)
Danke für den tollen Artikel!