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


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

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

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

    目录

    pull解析测试:

    pull解析例子:

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


    pull解析例子的源码:https://github.com/liuchenyang0515/PullDemo

    xml生成方式的源码:https://github.com/liuchenyang0515/SaveXmlInfo

    关于正常权限、危险权限及权限组的介绍见官方文档:https://developer.android.google.cn/guide/topics/security/permissions?utm_source=udacity&utm_medium=course&utm_campaign=android_basics#normal-dangerous

    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() + "
    ");
                }
                // 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=================================

    CSDN博客地址:https://blog.csdn.net/qq_34115899
  • 相关阅读:
    kotlin中值范围
    kotlin中集合
    kotlin数据解构
    Java 8 Lambda 表达式
    kotlin 之内联函数
    kotlin之函数的范围和泛型函数
    kotlin函数的参数和返回值
    kotlin 之单表达式函数
    kotlin使用中辍标记法调用函数
    kotlin之函数的基本用法
  • 原文地址:https://www.cnblogs.com/lcy0515/p/10807935.html
Copyright © 2020-2023  润新知