zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Microsoft Enterprise Library 5.0 系列(二) Cryptography Application Block (初级)
    Microsoft Enterprise Library 5.0 系列(五) Data Access Application Block
    Microsoft Enterprise Library 5.0 系列(八) Unity Dependency Injection and Interception
    Microsoft Enterprise Library 5.0 系列(九) Policy Injection Application Block
    Microsoft Enterprise Library 5.0 系列(三) Validation Application Block (高级)
    软件研发打油诗祝大家节日快乐
    从挖井的故事中想到开发管理中最容易忽视的几个简单道理
    ITIL管理思想的执行工具发布
    管理类软件设计“渔”之演化
    20070926日下午工作流与ITILQQ群 事件管理 讨论聊天记录
  • 原文地址:https://www.cnblogs.com/lcy0515/p/10807935.html
Copyright © 2011-2022 走看看