android學習筆記----pull解析與xml生成和應用申請權限模版
先來個pull解析測試,然后是pull解析用法說明,文章末尾附有xml生成方式。
學習目標:首先是解析測試例子給出的對于常用字段的理解,然后是pull解析常用套路方法,最后是xml的2種生成方式。
經常寫代碼需要申請動態權限,在最后例子也順帶記錄下來,方便查閱。
目錄
pull解析例子的源碼:https://github.com/liuchenyang0515/PullDemo
xml生成方式的源碼:https://github.com/liuchenyang0515/SaveXmlInfo
pull解析測試:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.Xml;
import android.view.View;
import org.xmlpull.v1.XmlPullParser;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onclick(View view) {
try {
InputStream is = getAssets().open("info.xml");
// 解析info.xml文件
// 1.得到xml文件的解析器
XmlPullParser parser = Xml.newPullParser();
// 2.設置輸入流和編碼
parser.setInput(is, "utf-8");
// 3.解析xml文件,獲取當前的事件類型
int type = parser.getEventType();
while (type != XmlPullParser.END_DOCUMENT){
// getText()是獲取內容,整個流程相當于一個指針指一次開始標簽再指一次內容,
// (內容的getName()為null, getText()才是取內容字符串,如果沒內容就是"")
// 再指一次結束標簽(如果沒遇到結束標簽就指向下一個開始標簽),然后再次指向內容,如果沒有getName()就是null
// 否則就指向下一個標簽,依次重復這個過程
// 經測試,事件類型依次為
// 最頂端document 0
// 開始標簽 2
// 內容 4
// 結束標簽 3
Log.d(TAG, parser.getEventType() + "..." + parser.getName() + "..." + parser.getText());
type = parser.next();
}
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
常用的字段:
int START_DOCUMENT = 0;
int END_DOCUMENT = 1;
int START_TAG = 2;
int END_TAG = 3;
int TEXT = 4;
筆記批注:
getText()是獲取內容,整個流程相當于一個指針指一次開始標簽再指一次內容,再指一次結束標簽(如果沒遇到結束標簽就指向下一個開始標簽),然后再次指向內容。
標簽只有getName(),而getText()為null, 內容只有getText(),而getName()為null。getText()取字符串,如果字符串就是空串"",說明本行后面沒內容了,那么就開始進行下一行的解析。
比如獲取開始標簽的下一次沒有內容而是另一個開始標簽,那么getText()是"",進行下一行解析,或者遇到結束標簽的下一次獲取內容getText()是"",說明后面沒內容了,進行下一行解析。
如果getName()沒有,值就是null(比如TEXT字段)
如下圖。
即不管是開始還是結束標簽,只要遇到標簽,下一次就會嘗試獲取內容,getEventType()得到了START_DOCUMENT和END_TAG字段, 那么下一次getEventType()一定是TEXT字段。
SAX解析和PULL解析原理是一樣的,可以見我另一篇博客之中寫到的SAX解析:SAX解析代碼原理分析
xml如下:
?
運行結果如下:
????
pull解析例子:
MainActivity.java:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import java.io.InputStream;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1.顯示天氣信息
TextView tv_weather = (TextView) findViewById(R.id.tv_weather);
try ( // 1.1獲取資產的管理者,通過上下文
InputStream inputStream = getAssets().open("weather.xml");) {
// 2.調用我們定義的解析xml業務方法
List<Channel> list = Weather.parserXml(inputStream);
StringBuffer sb = new StringBuffer();
for (Channel channel : list) {
sb.append(channel.toString() + "\n");
}
// 3.把數據展示到textview
tv_weather.setText(sb);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Weather.java:
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class Weather {
/**
* 服務器是以流的形式把數據返回的
*/
public static List<Channel> parserXml(InputStream in) throws Exception {
List<Channel> weatherLists = null;
Channel channel = null;
// 1.獲取XmlPullParser解析的實例
// 簡單工廠方法的例子
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
// 也可以直接一步寫XmlPullParser parser = Xml.newPullParser();
// 通過Xml.newPullParser()獲得的解析器可能會有一個bug:調用nextText()并不總是前進到END_TAG
// 一些app可能圍繞著這個問題,額外的調用next()或nextTag()方法:
// 在Android Ice Cream Sandwich版本中,刪除了ExpatPullParser類來修復這個bug,
// 不幸的是,app在Android4.0版本下使用它可能會導致應用crash
// 官方說明文檔是用的2步,如我上面寫的,就當做是推薦這種寫法吧
// 2.設置XmlPullParser參數
parser.setInput(in, "utf-8");
// 3.獲取事件類型
int type = parser.getEventType();
while (type != XmlPullParser.END_DOCUMENT) {
String nodeName = parser.getName();
// 4.具體判斷一下解析到哪里了
switch (type) {
case XmlPullParser.START_TAG:
if ("weather".equals(nodeName)) {
// 5.創建一個集合對象
weatherLists = new ArrayList<Channel>();
} else if ("channel".equals(nodeName)) {
// 6.創建Channel對象
channel = new Channel();
// 7.獲取id值
// 也可以parser.getAttributeValue(null, "id");
String id = parser.getAttributeValue(0); // 0號屬性
channel.setId(id);
} else if ("city".equals(nodeName)) {
// 8.獲取city的數據
String temp = parser.nextText();// 剛剛測試過了getText()是獲得本次指向的內容
// 這里nextText()就是指向下一次指向的內容,即開始和結束標簽之間的內容
channel.setTemp(temp);
} else if ("wind".equals(nodeName)) {
// 9.獲取wind數據
String wind = parser.nextText();
channel.setWind(wind);
} else if ("pm250".equals(nodeName)) {
// 9.獲取wind數據
String pm250 = parser.nextText();
channel.setPm250(pm250);
}
break;
case XmlPullParser.END_TAG:
if ("channel".equals(nodeName)) { // 已經測試過了,結束標簽不會讀取前面的/
// 把javabean對象存到集合中
weatherLists.add(channel);
}
break;
}
// 不停的向下解析
type = parser.next();
}
return weatherLists;
}
}
筆記批注:
// 1.獲取XmlPullParser解析的實例
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
這個就是簡單工廠方法模式的一個例子。
也可以直接一步寫XmlPullParser parser = Xml.newPullParser(); 通過Xml.newPullParser()獲得的解析器可能會有一個bug:調用nextText()并不總是前進到END_TAG一些app可能圍繞著這個問題,額外的調用next()或nextTag()方法:在Android Ice Cream Sandwich版本中,刪除了ExpatPullParser類來修復這個bug,不幸的是,app在Android4.0版本下使用它可能會導致應用crash, 官方說明文檔是用的2步,如我上面寫的,就當做是推薦這種寫法吧
詳情參考博客:https://blog.csdn.net/u013656135/article/details/49840125
關于方法使用:
getAttributeValue(int index);//大意就是返回指定位置的屬性值,位置從0開始
getAttributeValue(String namespace,String name); // 大意就是返回指定的屬性名對應的屬性值,如果沒有使用命名空間,則第一個參數傳入null,第二個參數是屬性名,這個例子是"id"屬性
Channel.java:
public class Channel {
private String id;
private String city;
private String temp;
private String wind;
private String pm250;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
public String getWind() {
return wind;
}
public void setWind(String wind) {
this.wind = wind;
}
public String getPm250() {
return pm250;
}
public void setPm250(String pm250) {
this.pm250 = pm250;
}
@Override
public String toString() {
return "Channel [id=" + id + ", city=" + city + ", temp=" + temp
+ ", wind=" + wind + ", pm250=" + pm250 + "]";
}
}
assets目錄下的weather.xml:
<?xml version="1.0" encoding="utf-8"?>
<weather>
<channel id='1'>
<city>北京</city>
<temp>25°</temp>
<wind>3</wind>
<pm250>300</pm250>
</channel>
<channel id='2'>
<city>鄭州</city>
<temp>20°</temp>
<wind>4</wind>
<pm250>300</pm250>
</channel>
<channel id='3'>
<city>長春</city>
<temp>10°</temp>
<wind>4</wind>
<pm250>100</pm250>
</channel>
<channel id='4'>
<city>沈陽</city>
<temp>20°</temp>
<wind>1</wind>
<pm250>50</pm250>
</channel>
</weather>
筆記批注:
assets與res/raw不同:
assets目錄是Android的一種特殊目錄,用于放置APP所需的固定文件,且該文件被打包到APK中時,不會被編碼到二進制文件。
Android還存在一種放置在res下的raw目錄,該目錄與assets目錄不同。
注意點:
1、 assets目錄不會被映射到R中,因此,資源無法通過R.id方式獲取,必須要通過AssetManager進行操作與獲取;res/raw目錄下的資源會被映射到R中,可以通過getResource()方法獲取資源。
2、 多級目錄:assets下可以有多級目錄,res/raw下不可以有多級目錄。
3、 編碼(都不會被編碼):assets目錄下資源不會被二進制編碼;res/raw應該也不會被編碼。
運行結果如下:
?
xml生成方式(代碼添加申請權限示范模版):
本demo的xml是一個Button,屬性添加android:onClick="save",然后幾個編輯框提交。如下圖:
?
SaveXmlInfo的Demo主要代碼如下:
import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Xml;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private EditText et_name, et_age, et_id;
private final int MY_PER_CODE = 15;
private String name, age, id;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_name = (EditText) findViewById(R.id.et_name);
et_age = (EditText) findViewById(R.id.et_age);
et_id = (EditText) findViewById(R.id.et_id);
}
public void save(View view) {
name = et_name.getText().toString().trim();
age = et_age.getText().toString().trim();
id = et_id.getText().toString().trim();
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(age) || TextUtils.isEmpty(id)) {
Toast.makeText(this, "信息不能為空", Toast.LENGTH_SHORT).show();
return;
} else {
List<String> permissionList = new ArrayList<>();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
if (!permissionList.contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
}
if (!permissionList.isEmpty()) {
String[] permissions = permissionList.toArray(new String[permissionList.size()]);
ActivityCompat.requestPermissions(this, permissions, MY_PER_CODE);
} else {
writeXml_1();
//writeXml_2();
}
}
}
private void writeXml_2() {
// 1.創建一個xml文件的序列化器
XmlSerializer serializer = Xml.newSerializer();
try (FileOutputStream fos = new FileOutputStream(new File
(Environment.getExternalStorageDirectory(), "info1.xml"));) {
// 設置文件的輸出和編碼方式
// 設置為使用具有給定編碼的二進制輸出流。
serializer.setOutput(fos, "utf-8");
// 寫xml文件的頭
// 使用編碼(if encoding not null)和獨立標志(if standalone not null)編寫<?xml聲明,此方法只能在setOutput之后調用。
serializer.startDocument("utf-8", true);
// 4.寫info結點
// 使用給定的命名空間和名稱寫入開始標記。如果沒有為給定的命名空間定義前綴,則將自動定義前綴。
// 如果名稱空間為NULL,則不打印名稱空間前綴,而只打印名稱。
serializer.startTag(null, "info");
// 5.寫student節點
serializer.startTag(null, "student");
// 6.寫屬性
// 寫一個屬性。對Attribute()的調用必須立即跟隨對startTag()的調用。
serializer.attribute(null, "id", id);
// 7.寫name
serializer.startTag(null, "name");
serializer.text(name);
serializer.endTag(null, "name");
// 8.寫age
serializer.startTag(null, "age");
serializer.text(age);
serializer.endTag(null, "age");
serializer.endTag(null, "student");
serializer.endTag(null, "info");
// 寫完。所有未關閉的開始標記將被關閉,輸出將被刷新。在調用此方法之后,在下次調用setOutput()之前,不能序列化更多的輸出。
serializer.endDocument();
Toast.makeText(this, "保存學生信息成功", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case MY_PER_CODE:
if (grantResults.length > 0) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) { // 如果用戶不同意
// ActivityCompat.shouldShowRequestPermissionRationale用法見下面筆記批注
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
showPermissionDialog(permissions);
} else { // 不同意的情況下還勾選了“不再提醒”
Toast.makeText(MainActivity.this, "您已拒絕權限,請在設置手動打開", Toast.LENGTH_SHORT).show();
}
return; // 用戶不同意的情況下,到這里直接返回
}
}
writeXml_1();
//writeXml_2();
} else {
Toast.makeText(this, "未知錯誤!", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
private void showPermissionDialog(final String[] permissions) {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle("提示!");
dialog.setMessage("這個權限關系到功能使用,如拒絕需要在設置手動打開!");
dialog.setCancelable(false); // 點擊空白處不可取消
dialog.setPositiveButton("授權", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
ActivityCompat.requestPermissions(MainActivity.this, permissions, MY_PER_CODE);
}
});
dialog.setNegativeButton("拒絕", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
dialog.show();
}
private void writeXml_1() {
StringBuffer sb = new StringBuffer();
sb.append("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>");
sb.append("<info>");
sb.append("<student id=\"" + id + "\">");
sb.append("<name>" + name + "</name>");
sb.append("<age>" + age + "</age>");
sb.append("</student>");
sb.append("</info>");
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(new File(Environment.getExternalStorageDirectory(), "info.xml"))));) {
bw.write(sb.toString());
Toast.makeText(this, "保存學生信息成功", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "保存學生信息失敗", Toast.LENGTH_SHORT).show();
}
}
}
writeXml_1()是通過文件寫xml,writeXml_2()是通過序列化器寫xml。
筆記批注:
ActivityCompat.shouldShowRequestPermissionRationale用法:
應用安裝后第一次訪問,如果未開始獲取權限申請直接返回false;可能此時并未請求權限而執行到此方法
第一次請求權限時,用戶拒絕了,下一次shouldShowRequestPermissionRationale()返回 true,這時候可以顯示一些為什么需要這個權限的說明;
第二次以及之后請求權限時,用戶拒絕了,并選擇了“不再提醒”的選項時:shouldShowRequestPermissionRationale()返回 false;
第二次以及之后請求權限時,用戶拒絕了,但沒有勾選“不再提醒”選項,返回true,繼續提醒
設備的系統設置中禁止當前應用獲取這個權限的授權,shouldShowRequestPermissionRationale()返回false;
=================================Talk is cheap, show me the code=================================
智能推薦
Android權限申請的學習實踐
1.引子 在換到Android手機之前,對Android系統的印象是這系統app的跑馬場,app可以任意索取各種權限,隨意竊取各種隱私,換手機后才知道Android系統對權限的管理已有很大的改觀,索取的每個危險權限都需要提示用戶,當然Android只是盡可能提示用戶,還是存在著用戶不同意就不給用的情況。 權限管理的改進給開發者增加了一定工作量,申請危險權限不再是簡單的在AndroidManifes...
Android使用Pull方式進行Xml文件解析
熟知的XML解析方式有SAX,DOM,Pull三種,筆者采用Pull方式對xml進行了解析操作。 以案例入手學習: 對本地xml文件進行解析,首先把xml文件放入到assets文件夾下。 1.xml文件格式及內容: (在這個xml文件中,我們發現在根標簽之中有:與,而且point在stroke之中,這種情況我們需要注意一下,首先把point內的x,y,time放入到集合中,再把point加上str...
[Android面試] Xml 解析辨析---SAX、DOM、Pull
在Android中提供了三種解析XML的方式: SAX(Simple API XML),DOM(Document Objrect Model),以及Android推薦的Pull解析方式。 解析概念 SAX: 是事件驅動型XML解析的一個標準接口,簡單地說就是對文檔進行順序掃描,當掃描到文檔(document)開始與結束、元素(element)開始與結束、文檔(document)結束等...
Android開發:XML簡介及DOM、SAX、PULL解析對比
目錄 目錄 定義 XML(extensible Markup Language) ,是一種數據標記語言 & 傳輸格式 作用 對數據進行標記(結構化數據) 對數據進行存儲 對數據進行傳輸 與html的區別:html用于顯示信息;xml用于存儲&傳輸信息 XML特點 標簽可進行自定義 XML允許作者定義自己的標簽和文檔結構 自我描述性 XML文檔實例 僅僅是一個純文本,有文本處理能力的...
Android --pull解析XML,獲取省市區的例子
舉個這樣的例子,這是一個XML的部分數據,現在需要解析 最后的效果圖 寫這個帖子是記錄下,解析這塊,說實話,我居然忘記怎么解析了,還是后面百度別人的文章,跟看同事的代碼,所以基礎啊 不牢固 ...
猜你喜歡
Android使用Sax 及Pull 解析Xml demo
客戶端開發中,越來越多的接口使用的數據傳輸類型時json了,xml相對較少。恰好當前要使用到了xml傳遞數據,寫個demo權當記錄安卓中如何使用sax 及 pull解析xml. 1.常用的幾種解析XML的方式 Dom ,Sax ,Pull Dom解析Xml 是將整個Xm文檔當成一個對象來處理,會先把整個文檔讀入到內存里。是基于樹的結構,通常需要加載整文檔和構造DOM樹,然...
【Unity&Android】安卓app自測應用隱私相關獲取和申請權限
目錄 ① 在雷神模擬器4上-設置-開啟Root權限和USB權限,安裝你要自測的app。 ② 安裝python3 (或最新版本) ③ window安裝frida-server ④ 安裝手機使用的frida文件并運行起來 ⑤ 下載camille【Hook工具】 注意事項 通常,需要這種需求的情況就是因為XXXX應用獲取了XXX隱私或申請了XXX隱私權限導致拒審。 然后呢,我們就要去查到底是哪里獲取了或...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...