Android网络技术:访问网络、解析XML和JSON格式文件

目录:
1.WebView组件的使用
2.使用HttpURLConnection协议访问网络
3.通过HttpClient访问
4.解析XML格式数据
5.解析JSON格式数据
————————————————————————《第一行代码》笔记之网络技术

1.WebView组件的使用

该组件的使用需要在AndroidManifest.xml添加网络权限,如下:

    <uses-permission android:name="android.permission.INTERNET"/>

1.1用法一:
布局文件:只添加一个WebView的控件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

MainActivity文件中的代码:

public class MainActivity extends Activity {
//声明组件
    private WebView webView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取实例
        webView = (WebView) findViewById(R.id.web_view);
        //通过Setting()方法调整一些属性,此处以设置JS功能可用为例
        webView.getSettings().setJavaScriptEnabled(true);
        /*此步比较重要调用了WebView的setWebViewClient()方法,并传入了WebViewClient的匿名类作为参数,然后重写了shouldOverrideUrlLoading()方法,这就表明当需要从一个网页跳转到另一个网页时,我们希望目标网页仍然在当前WebView中显示,而不是打开系统浏览器。*/
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url); // 根据传入的参数再去加载新的网页
                return true; // 表示当前WebView可以处理打开新网页的请求,不用借助系统浏览器
            }
        });
        //调用WebView的loadUrl()方法,并将网址传入,即可展示相应网页的内容
        webView.loadUrl("http://www.baidu.com");
    }

}

1.2用法二:
该用法实现了通过百度进行关键词搜索,并将搜索到页面显示在WebView中
布局的设计与用法一有所不同,此布局中多加入了Button、EditText等插件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.a14553.webview.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/txv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="请输入关键词:" />

            <EditText
                android:id="@+id/edt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ems="10"
                android:inputType="textPersonName" />

            <Button
                android:id="@+id/btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="search"
                android:text="Button" />
        </LinearLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <WebView
                android:id="@+id/wv"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            </WebView>

            <ProgressBar
                android:id="@+id/progressBar"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="-7dp"
                tools:layout_editor_absoluteX="135dp"
                tools:layout_editor_absoluteY="0dp" />
        </RelativeLayout>
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

MainActivit.java中的代码如下

package com.example.a14553.webview;

import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.ProgressBar;

public class MainActivity extends AppCompatActivity {
    WebView wv;
    ProgressBar pb;
    EditText edt;
    String keyword;
    String baseURL = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd=";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实例化
        wv = (WebView)findViewById(R.id.wv);
        pb = (ProgressBar)findViewById(R.id.progressBar);
        edt = (EditText)findViewById(R.id.edt);
        //使JS功能可用
        wv.getSettings().setJavaScriptEnabled(true);
        //避免打开浏览器
        wv.setWebViewClient(new WebViewClient());
        //创建网页加载进度接口
        wv.setWebChromeClient(new WebChromeClient(){
            public void onProgressChanged(WebView view,int progress){
            //为菜单条绑定加载进度
                pb.setProgress(progress);
                pb.setVisibility(progress<100?View.VISIBLE:View.GONE);
            }
        });
    }
    //点击按钮触发事件
    public void search(View v){
    //将单个或连续的空格置换成+
        keyword = edt.getText().toString().replaceAll("\\s+","+");
        wv.loadUrl(baseURL+keyword);
    }
    //返回上一页的功能,按返回键触发
    public void onBackPressed(){
        if(wv.canGoBack()){
            wv.goBack();
            return;
        }
        super.onBackPressed();
    }
    //通过首选项将参数字符串存储起来
    public void onPause(){
        super.onPause();
        SharedPreferences.Editor editor = getPreferences(MODE_PRIVATE).edit();
        //写入字符串
        editor.putString("关键词",keyword);
        //提交,也可使用editor.commit()方法
        editor.apply();
    }
    //读取首选项
    public void onResume(){
        super.onResume();
        //获取首选项对象
        SharedPreferences myPref = getPreferences(MODE_PRIVATE);
        //读取存储的字符串项,若不存在则默认返回welcome
        keyword = myPref.getString("关键字","welcome");
        if(wv.getUrl()==null)
            wv.loadUrl(baseURL+keyword);
    }
}

2.使用HttpURLConnection协议访问网络

使用HTTP协议访问网络的时候需要使用到多线程,Android中使用多线程更多的是使用匿名类的方式:

new Thread(new Runnable() {

    @Override
    public void run() {
        // 处理具体的逻辑
    }

}).start();

而在子线程中是不可以修改UI界面的内容的,也就是说在子线程从网络中获得的数据无法直接输出到EditText中的,所以需要使用异步处理:
①其中一个方法是可以通过Handler来实现

private Handler handler = new Handler() {

        public void handleMessage(Message msg) {
            switch (msg.what) {
            case UPDATE_TEXT:
                // 在这里可以进行UI操作
                text.setText("Nice to meet you");
                break;
            default:
                break;
            }
        }

    };

//与之配套的是在子线程中返回信息
//子线程内:
Message message = new Message();
message.what = SHOW_RESPONSE;
// 将服务器返回的结果存放到Message中
message.obj = response.toString();
handler.sendMessage(message);

②另一种方法是通过AsyncTask抽象类来实现
一个简单的自定义的AsyncTask如下

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    //第一个参数为传入的参数,这里为void
    //第二个参数是处理的进度,这里用int类型的数据
    //第三个参数是返回的结果,这里返回Boolean类型的结果
    @Override
    protected void onPreExecute() {
        progressDialog.show(); // 显示进度对话框
    }
//在此方法中不能进行UI操作,如果需要UI操作则使用publishProcess调用下面的函数
    @Override
    protected Boolean doInBackground(Void... params) {
        try {
            while (true) {
                int downloadPercent = doDownload(); // 这是一个虚构的方法
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }
//在此方法中可以进行UI操作
    @Override
    protected void onProgressUpdate(Integer... values) {
        // 在这里更新下载进度
        progressDialog.setMessage("Downloaded " + values[0] + "%");
    }
//此方法最后返回结果
    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss(); // 关闭进度对话框
        // 在这里提示下载结果
        if (result) {
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show();
        }
    }

}
如果想要启动这个任务,只需编写以下代码即可:
new DownloadTask().execute();

本处将使用第一种方法
2.1.首先实例化一个可以异步修改UI的对象handler

    private Handler handler = new Handler() {

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW_RESPONSE:
                    String response = (String) msg.obj;
                    // 在这里进行UI操作,将结果显示到界面上
                    responseText.setText(response);
            }
        }

    };

2.2.相应鼠标的事件

public void onClick(View v){
        if (v.getId() == R.id.send_request) {
            sendRequestWithHttpURLConnection();
        }
    }

2.3.新建线程读取文件流并通过message传回

private void sendRequestWithHttpURLConnection() {
        // 开启线程来发起网络请求
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                try {
                    URL url = new URL(path);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);
                    InputStream in = connection.getInputStream();
                    // 下面对获取到的输入流进行读取
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        response.append(line);
                    }
                    Message message = new Message();
                    message.what = SHOW_RESPONSE;
                    // 将服务器返回的结果存放到Message中
                    message.obj = response.toString();
                    handler.sendMessage(message);

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }

3.通过HttpClient访问

HttpClient是一个接口,所以不能实例化,一般用DefaultHttpClient()去实例化
HttpClient也有GET和POST两种方法:
其中GET方法较简单:

//创建一个HttpGet对象,并传入目标的网络地址
HttpGet httpGet = new HttpGet("http://www.baidu.com");
//调用HttpClient的execute()方法
httpClient.execute(httpGet);

POST方法较复杂:

//创建一个HttpPost对象,并传入目标的网络地址
HttpPost httpPost = new HttpPost("http://www.baidu.com");
//通过一个NameValuePair集合来存放待提交的参数
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("username", "admin"));
params.add(new BasicNameValuePair("password", "123456"));
//将这个参数集合传入到一个UrlEncodedFormEntity中
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "utf-8");
//调用HttpPost的setEntity()方法将构建好的UrlEncodedFormEntity传入
httpPost.setEntity(entity);
//调用HttpClient的execute()方法
httpClient.execute(httpPost);

执行execute()方法之后会返回一个HttpResponse对象,服务器所返回的所有信息就会包含在这里面。通常情况下我们都会先取出服务器返回的状态码,如果等于200就说明请求和响应都成功了:

if (httpResponse.getStatusLine().getStatusCode() == 200) {
    // 请求和响应都成功了
    //调用getEntity()方法获取到一个HttpEntity实例
    HttpEntity entity = httpResponse.getEntity();
    //用EntityUtils.toString()这个静态方法将HttpEntity转换成字符串
    String response = EntityUtils.toString(entity,"UTF-8");

}

与使用HttpURLConnection相比,使用HttpClient只需要添加一个方法,并在点击按钮时使用本方法即可:
PS:Android6.0+已经不再支持该类。

private void sendRequestWithHttpClient() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    HttpClient httpClient = new DefaultHttpClient();
                    HttpGet httpGet = new HttpGet("http://www.baidu.com");
                    HttpResponse httpResponse = httpClient.execute(httpGet);
                    if (httpResponse.getStatusLine().getStatusCode() == 200) {
                        // 请求和响应都成功了
                        HttpEntity entity = httpResponse.getEntity();
                        String response = EntityUtils.toString(entity, "utf-8");
                        Message message = new Message();
                        message.what = SHOW_RESPONSE;
                        // 将服务器返回的结果存放到Message中
                        message.obj = response.toString();
                        handler.sendMessage(message);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

4.解析XML格式数据

解析XML格式的数据的方式:DOM解析、Pull解析、Sax解析等。
4.1通过DOM解析
先通过sendRequestWithHttpURLConnectionXmlPull()方法连接网络并打开文件流

public void sendRequestWithHttpURLConnectionXmlPull(){
        // 开启线程来发起网络请求
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                try {
                    URL url = new URL(path_json);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);
                    InputStream in = connection.getInputStream();
                    String temp=parseXmlByDom(in);
                    Message message = new Message();
                    message.what = SHOW_RESPONSE;
                    // 将服务器返回的结果存放到Message中
                    message.obj = temp;
                    handler.sendMessage(message);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }

具体的解析方法parseXmlByDom():

public String parseXmlByDom(InputStream inputStream){
        String result = "";
        //
        DocumentBuilderFactory bdf = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = null;
        Document doc = null;
        try{
            //
            builder = bdf.newDocumentBuilder();
        }catch (ParserConfigurationException e){
            e.printStackTrace();
        }
        try{
            //解析字符流
            doc = builder.parse(inputStream);
        }catch (SAXException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
        Element ele = doc.getDocumentElement();
        //获取所有的fruit节点

        NodeList nl = ele.getElementsByTagName("fruit");
        if(nl!=null&&nl.getLength()!=0){
            for(int i=0;i<nl.getLength();i++){
                Element entry = (Element)nl.item(i);
                //
                result += "name:"+entry.getAttribute("name")+"-->"+entry.getTextContent()+"\n";
            }
        }
        return result;
    }

4.2Pull解析
此处使用HttpURLConnection协议访问网络,程序与2中的程序相同,有差别的是修改了2中的sendRequestWithHttpURLConnection()方法,并且将按钮绑定的事件修改为调用新方法,新方法如下:

public void sendRequestWithHttpURLConnectionXmlPull(){
        // 开启线程来发起网络请求
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                try {
                    URL url = new URL(path);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);
                    InputStream in = connection.getInputStream();
                    // 下面对获取到的输入流进行读取
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        response.append(line);
                    }
                    String res = response.toString();
                    String temp=parseXMLWithPull(res);
                    Message message = new Message();
                    message.what = SHOW_RESPONSE;
                    // 将服务器返回的结果存放到Message中
                    message.obj = temp;
                    handler.sendMessage(message);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }
    private String parseXMLWithPull(String xmlData) {
        String res = "";
        try {
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser xmlPullParser = factory.newPullParser();
            xmlPullParser.setInput(new StringReader(xmlData));
            int eventType = xmlPullParser.getEventType();
            String id = "";
            String name = "";
            String version = "";
            while (eventType != XmlPullParser.END_DOCUMENT) {
                String nodeName = xmlPullParser.getName();
                switch (eventType) {
                    // 开始解析某个结点
                    case XmlPullParser.START_TAG: {
                        if ("id".equals(nodeName)) {
                            id = xmlPullParser.nextText();
                            res+="id="+id+"\n";
                        } else if ("name".equals(nodeName)) {
                            name = xmlPullParser.nextText();
                            res+="name"+name+"\n";
                        } else if ("version".equals(nodeName)) {
                            version = xmlPullParser.nextText();
                            res+="version="+version+"\n";
                        }
                        break;
                    }
                    // 完成解析某个结点
                    case XmlPullParser.END_TAG: {
                        if ("app".equals(nodeName)) {
                            Log.d("MainActivity", "id is " + id);
                            Log.d("MainActivity", "name is " + name);
                            Log.d("MainActivity", "version is " + version);
                        }
                        break;
                    }
                    default:
                        break;
                }
                eventType = xmlPullParser.next();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return res;
    }

本程序使用的示例为:
get_data.xml

<apps>
    <app>
        <id>1</id>
        <name>Google Maps</name>
        <version>1.0</version>
    </app>
    <app>
        <id>2</id>
        <name>Chrome</name>
        <version>2.1</version>
    </app>
    <app>
        <id>3</id>
        <name>Google Play</name>
        <version>2.3</version>
    </app>
</apps>

4.3Sax解析
与4.2的程序相比,本程序新建了一个ContentHandler 类负责解析,并新建一个方法负责该类的调用,最后便是修改sendRequestWithHttpURLConnectionXmlPull()方法中一个调用,修改如下:

//String temp=parseXMLWithPull(res);
String temp=parseXMLWithSAX(res);

ContentHandler 类内容如下:

package com.example.a14553.httpconntest;

import android.util.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
 * Created by 14553 on 2018/3/8.
 */
public class ContentHandler extends DefaultHandler {
    public String res="";
    private String nodeName;
    private StringBuilder id;
    private StringBuilder name;
    private StringBuilder version;
    @Override
    public void startDocument() throws SAXException {
        id = new StringBuilder();
        name = new StringBuilder();
        version = new StringBuilder();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        // 记录当前结点名
        nodeName = localName;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        // 根据当前的结点名判断将内容添加到哪一个StringBuilder对象中
        if ("id".equals(nodeName)) {
            id.append(ch, start, length);
        } else if ("name".equals(nodeName)) {
            name.append(ch, start, length);
        } else if ("version".equals(nodeName)) {
            version.append(ch, start, length);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if ("app".equals(localName)) {
            res += "id"+id.toString()+"\n";
            res += "name"+name.toString()+"\n";
            res += "version"+version.toString()+"\n";
            Log.d("ContentHandler", "id is " + id.toString().trim());
            Log.d("ContentHandler", "name is " + name.toString().trim());
            Log.d("ContentHandler", "version is " + version.toString().trim());
            // 最后要将StringBuilder清空掉
            //res = id.toString()+name.toString()+version.toString();
            id.setLength(0);
            name.setLength(0);
            version.setLength(0);
        }
    }
    public String backString(){

        return res;
    }
    @Override
    public void endDocument() throws SAXException {
    }

}

新建的方法parseXMLWithSAX()为:

private String parseXMLWithSAX(String xmlData) {
        String res="";
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            XMLReader xmlReader = factory.newSAXParser().getXMLReader();
            ContentHandler handler = new ContentHandler();
            // 将ContentHandler的实例设置到XMLReader中
            xmlReader.setContentHandler(handler);
            // 开始执行解析
            xmlReader.parse(new InputSource(new StringReader(xmlData)));
            res = handler.backString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

5.解析JSON格式数据

解析JSON的方式:通过JSONObject、通过GSON
5.1.使用JSONObject
仍使用上面的项目,区别在于新增一个方法parseJSONWithJSONObject(),修改了sendRequestWithHttpURLConnectionXmlPull()方法中调用解析方式的地方:

//String temp=parseXMLWithSAX(res);
//String temp=parseXMLWithPull(res);
String temp=parseJSONWithJSONObject(res);               

parseJSONWithJSONObject()方法内容如下:

private String parseJSONWithJSONObject(String jsonData) {
        String res="JSON\n";
        try {
            JSONArray jsonArray = new JSONArray(jsonData);
            for (int i = 0; i < jsonArray.length(); i++) {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                String id = jsonObject.getString("id");
                String name = jsonObject.getString("name");
                String version = jsonObject.getString("version");
                Log.d("MainActivity", "id is " + id);
                Log.d("MainActivity", "name is " + name);
                Log.d("MainActivity", "version is " + version);
                res+="id="+id+"\n";
                res+="name="+name+"\n";
                res+="version="+version+"\n";
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

用于测试的文件:get_data.json

[{"id":"5","version":"5.5","name":"Angry Birds"},
{"id":"6","version":"7.0","name":"Clash of Clans"},
{"id":"7","version":"3.5","name":"Hey Day"}]

5.2.使用GSON解析JSON数据
GSON没有被添加到Android官方的API中,因此如果想要使用这个功能的话,则必须要在项目中添加一个GSON的Jar包。首先需要将GSON的资源压缩包下载下来(gson-2.8.2.jar),下载地址是:http://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.2/
将gson-2.8.2.jar复制进项目的libs文件夹中,右击,Add As Library即可。
之后新建App类:

package com.example.a14553.httpconntest;

/**
 * Created by 14553 on 2018/3/8.
 */

public class App {

    private String id;

    private String name;

    private String version;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

}

在MainActivity类中添加方法parseJSONWithGSON(),并修改了sendRequestWithHttpURLConnectionXmlPull()方法中调用解析方式的地方:

String temp=parseJSONWithGSON(res);
//String temp=parseJSONWithJSONObject(res);
//String temp=parseXMLWithSAX(res);
//String temp=parseXMLWithPull(res);

parseJSONWithGSON()方法如下:

private String parseJSONWithGSON(String jsonData) {
        String res="";
        Gson gson = new Gson();
        List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>() {}.getType());
        for (App app : appList) {
            Log.d("MainActivity", "id is " + app.getId());
            Log.d("MainActivity", "name is " + app.getName());
            Log.d("MainActivity", "version is " + app.getVersion());
            res+="id="+app.getId()+"\n";
            res+="name="+app.getName()+"\n";
            res+="version="+app.getVersion()+"\n";
        }
        return res;
    }

源工程代码

猜你喜欢

转载自blog.csdn.net/s1455364690/article/details/79490371