На сегодняшний день роль XML (eXtensible Markup Language – расширяемый язык разметки) в программировании огромна: этот формат используется повсеместно во многих языках программирования. В частности, при разработке приложений на Android XML отвечает за то, что пользователь увидит на экране своего смартфона (интерфейс приложения, строковые ресурсы, векторные изображения и т.д.). Кроме того, данный формат зачастую используется для хранения и передачи различных данных.
Именно из-за того, что XML можно использовать как хранилище данных, встаёт вопрос: как с этими данными взаимодействовать, как получать их из XML и отправлять обратно.
Ранее мы говорили о том, что в Android 8.0 Oreo для хранения конфигураций Wi-Fi сетей теперь используется другой файл (подробнее об этом можно почитать здесь). Поскольку этот файл конфигурации как раз нужного нам формата XML, попробуем распарсить его и вытащить их него нужные данные.
Сама структура XML-файла конфигурации выглядит следующим образом:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <WifiConfigStoreData> <int name="Version" value="1" /> <NetworkList> <Network> <WifiConfiguration> <string name="ConfigKey">"Kafe Mongol"WPA_PSK</string> <string name="SSID">"Kafe Mongol"</string> <null name="BSSID" /> <string name="PreSharedKey">"mongol1234"</string> <null name="WEPKeys" /> <int name="WEPTxKeyIndex" value="0" /> <boolean name="HiddenSSID" value="false" /> <boolean name="RequirePMF" value="false" /> <byte-array name="AllowedKeyMgmt" num="1">02</byte-array> <byte-array name="AllowedProtocols" num="1">03</byte-array> <byte-array name="AllowedAuthAlgos" num="1">01</byte-array> <byte-array name="AllowedGroupCiphers" num="1">0f</byte-array> <byte-array name="AllowedPairwiseCiphers" num="1">06</byte-array> <boolean name="Shared" value="true" /> <int name="Status" value="2" /> <null name="FQDN" /> <null name="ProviderFriendlyName" /> <null name="LinkedNetworksList" /> <string name="DefaultGwMacAddress">6c:19:8f:f5:e3:6e</string> <boolean name="ValidatedInternetAccess" value="true" /> <boolean name="NoInternetAccessExpected" value="false" /> <int name="UserApproved" value="0" /> <boolean name="MeteredHint" value="false" /> <boolean name="UseExternalScores" value="false" /> <int name="NumAssociation" value="4" /> <int name="CreatorUid" value="1000" /> <string name="CreatorName">android.uid.system:1000</string> <string name="CreationTime">time=08-30 12:51:06.957</string> <int name="LastUpdateUid" value="1000" /> <string name="LastUpdateName">android.uid.system:1000</string> <int name="LastConnectUid" value="1000" /> <boolean name="IsLegacyPasspointConfig" value="false" /> <long-array name="RoamingConsortiumOIs" num="0" /> </WifiConfiguration> <NetworkStatus> <string name="SelectionStatus">NETWORK_SELECTION_ENABLED</string> <string name="DisableReason">NETWORK_SELECTION_ENABLE</string> <null name="ConnectChoice" /> <long name="ConnectChoiceTimeStamp" value="-1" /> <boolean name="HasEverConnected" value="true" /> </NetworkStatus> <IpConfiguration> <string name="IpAssignment">DHCP</string> <string name="ProxySettings">NONE</string> </IpConfiguration> </Network> ... </NetworkList> <PasspointConfigData> <long name="ProviderIndex" value="0" /> </PasspointConfigData> </WifiConfigStoreData>
Нужные нам данные хранятся внутри тега <WifiConfiguration>, каждый тег обозначает тип значения, затем внутри в виде атрибутов устанавливаются его название и собственно значение.
Для извлечения данных в Android есть полезный интерфейс XmlPullParser. С его помощью можно в цикле while пройтись построчно по каждому тегу в документе и проверить его содержимое. Каждый проход по циклу представляет собой определённое событие, метод getEventType() возвращает тип этого события, например: START_DOCUMENT, END_TAG и др. Переход к следующему тегу осуществляется с помощью метода next().
Извлечём некоторые данные из XML файла. Получившийся код выглядит следующим образом:
private void parseWithXmlPullParser() { LayoutInflater inflater = getLayoutInflater(); CardView cardView = null; LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.setMargins(50, 50, 50, 50); try { XmlPullParser xmlPullParser = getResources().getXml(R.xml.wifi_config_store); while (xmlPullParser.getEventType() != XmlPullParser.END_DOCUMENT) { switch (xmlPullParser.getEventType()) { case XmlPullParser.START_DOCUMENT: { if (BuildConfig.DEBUG) { Log.d("LOG_TAG", "START_DOCUMENT"); } break; } // начало тега case XmlPullParser.START_TAG: { if (BuildConfig.DEBUG) { Log.d("LOG_TAG", "START_TAG: имя тега = " + xmlPullParser.getName() + ", уровень = " + xmlPullParser.getDepth() + ", число атрибутов = " + xmlPullParser.getAttributeCount()); } if (xmlPullParser.getName().equals("WifiConfiguration")) { cardView = (CardView) inflater.inflate(R.layout.wifi_info, null); cardView.setLayoutParams(params); } if (xmlPullParser.getName().equals("string")) { if (xmlPullParser.getAttributeValue(null, "name").equals("SSID")) { TextView textViewSSID = cardView.findViewById(R.id.textSsid); textViewSSID.setText(textViewSSID.getText() + " " + xmlPullParser.nextText()); break; } if (xmlPullParser.getAttributeValue(null, "name").equals("PreSharedKey")) { TextView textViewPreShareKey = cardView.findViewById(R.id.textPreShareKey); textViewPreShareKey.setText( textViewPreShareKey.getText() + " " + xmlPullParser.nextText()); break; } } if (xmlPullParser.getName().equals("boolean")) { if (xmlPullParser.getAttributeValue(null, "name").equals("HiddenSSID")) { TextView textViewHiddenSSID = cardView.findViewById(R.id.textHiddenSsid); textViewHiddenSSID.setText( textViewHiddenSSID.getText() + " " + xmlPullParser.getAttributeValue(null, "value")); break; } } break; } // конец тега case XmlPullParser.END_TAG: if (BuildConfig.DEBUG) { Log.d("LOG_TAG", "END_TAG: имя тега = " + xmlPullParser.getName()); } if (xmlPullParser.getName().equals("WifiConfiguration")) { linearLayout.addView(cardView); } break; // содержимое тега case XmlPullParser.TEXT: if (BuildConfig.DEBUG) { Log.d("LOG_TAG", "текст = " + xmlPullParser.getText()); } break; default: break; } xmlPullParser.next(); } } catch (Throwable e) { e.printStackTrace(); } }
В данном случае нас интересует событие START_TAG. Когда это событие наступает, нужно найти нужные данные по типу и имени, и затем забрать из этого тега значение. В результате выполнения цикла выводим всё на экран.
Как видно, реализация довольно проста, разработчику достаточно только понимать, как устроен XML-документ.
Однако это не единственный способ извлечь данные из XML. В Android есть также класс DocumentBuilder, который позволяет получать экземпляры DOM документов из XML DOM (Document Object Model) – это программный интерфейс, благодаря которому можно получать доступ к данным HTML или XML.
Ключевым методом здесь будет являться getElementsByTagName(), который возвращает список тегов с нужным именем, затем внутри этого списка ищется нужный по атрибуту name. Реализация выглядит следующим образом.
private void parseWithDOM() { LayoutInflater inflater = getLayoutInflater(); CardView cardView = null; LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.setMargins(50, 50, 50, 50); try { InputStream is = getAssets().open("wifi_config_store.xml"); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document doc = documentBuilder.parse(is); Element element = doc.getDocumentElement(); element.normalize(); NodeList nList = doc.getElementsByTagName("WifiConfiguration"); for (int i = 0; i < nList.getLength(); i++) { cardView = (CardView) inflater.inflate(R.layout.wifi_info, null); cardView.setLayoutParams(params); TextView textViewSSID = cardView.findViewById(R.id.textSsid); TextView textViewPreSharedKey = cardView.findViewById(R.id.textPreShareKey); TextView textViewHiddenSSID = cardView.findViewById(R.id.textHiddenSsid); Node node = nList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { Element elementNode = (Element) node; NodeList childrenList = elementNode.getElementsByTagName("string"); for (int j = 0; j < childrenList.getLength(); j++) { Element childNode = (Element) childrenList.item(j); if (childNode.getAttribute("name").equals("SSID")) { textViewSSID.setText(textViewSSID.getText() + " " + childNode.getChildNodes().item(0).getNodeValue()); } if (childNode.getAttribute("name").equals("PreSharedKey")) { textViewPreSharedKey.setText(textViewPreSharedKey.getText() + " " + childNode.getChildNodes().item(0).getNodeValue()); } } NodeList hiddenList = elementNode.getElementsByTagName("boolean"); for (int k = 0; k < hiddenList.getLength(); k++) { Element childNode = (Element) hiddenList.item(k); if (childNode.getAttribute("name").equals("HiddenSSID")) { textViewHiddenSSID.setText(textViewHiddenSSID.getText() + " " + childNode.getAttribute("value")); } } } linearLayout.addView(cardView); } } catch (IOException | ParserConfigurationException | SAXException e) { e.printStackTrace(); } }
В результате мы получили аналогичный список, как и при первом случае.
Здесь были приведены не все способы распарсить XML, существует множество различных пользовательских библиотек, предоставляющих такой же функционал. Всё зависит от того, какой способ будет удобнее разработчику в конкретной ситуации.
Спасибо за хорошую статью. Жаль, раньше на нее не попал и парсил документ с помощью строковых методов java)))
Спасибо за хороший пример XML файла