android学习笔记----pull解析与xml生成和应用申请权限模版

先来个pull解析测试,然后是pull解析用法说明,文章末尾附有xml生成方式。

学习目标:首先是解析测试例子给出的对于常用字段的理解,然后是pull解析常用套路方法,最后是xml的2种生成方式。

经常写代码需要申请动态权限,在最后例子也顺带记录下来,方便查阅。

目录

pull解析测试:

pull解析例子:

xml生成方式(代码添加申请权限示范模版):



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()是获取内容,整个流程相当于一个指针指一次开始标签再指一次内容,再指一次结束标签(如果没遇到结束标签就指向下一个开始标签),然后再次指向内容。

标签的getText()为null, 内容的getName()为null。getText()取字符串,如果没内容字符串就是""(比如获取开始标签的下一次没有内容而是另一个开始标签getText()一定是"",或者遇到结束标签的下一次getText()一定是""),如果getName()没有就是null(比如TEXT字段),否则就指向下一个标签,依次重复这个过程。如下图。

即不管是开始还是结束标签,只要遇到标签,下一次就会尝试获取内容,getEventType()得到了START_DOCUMENT和END_TAG字段, 那么下一次getEventType()一定是TEXT字段。

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) {
            // 4.具体判断一下解析到哪里了
            switch (type) {
                case XmlPullParser.START_TAG:
                    if ("weather".equals(parser.getName())) {
                        // 5.创建一个集合对象
                        weatherLists = new ArrayList<Channel>();
                    } else if ("channel".equals(parser.getName())) {
                        // 6.创建Channel对象
                        channel = new Channel();
                        // 7.获取id值
                        // 也可以parser.getAttributeValue(null, "id");
                        String id = parser.getAttributeValue(0); // 0号属性
                        channel.setId(id);
                    } else if ("city".equals(parser.getName())) {
                        // 8.获取city的数据
                        String temp = parser.nextText();// 刚刚测试过了getText()是获得本次指向的内容
                        // 这里nextText()就是指向下一次指向的内容,即开始和结束标签之间的内容
                        channel.setTemp(temp);
                    } else if ("wind".equals(parser.getName())) {
                        // 9.获取wind数据
                        String wind = parser.nextText();
                        channel.setWind(wind);
                    } else if ("pm250".equals(parser.getName())) {
                        // 9.获取wind数据
                        String pm250 = parser.nextText();
                        channel.setPm250(pm250);
                    }
                    break;
                case XmlPullParser.END_TAG:
                    if ("channel".equals(parser.getName())) { // 已经测试过了,结束标签不会读取前面的/
                        // 把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=======================================


猜你喜欢

转载自blog.csdn.net/qq_34115899/article/details/80933772