安卓学习笔记4——简易新闻客户端

一、项目知识点:

1、简易服务器搭建,内容准备

本文使用简单的Tomcat服务器,配置方法参考博文:
https://blog.csdn.net/qq_40881680/article/details/83582484#Tomcat

注意:
(1)安装JRE或者JDK,并在系统环境变量中加入JDK_HOME变量D:\Java\jdk1.8.0_231
(2)服务器启动,报错等详见该博文

安装后的效果:
cmd
在这里插入图片描述
Tomcat
在这里插入图片描述
打开本地服务器网页:localhost:8080
在这里插入图片描述
内容准备:将提前准备的图片和xml文件放置在webapps的root根目录下
在这里插入图片描述
安卓手机将要访问该news.xml文件,并根据该xml文件进行解析;获取图片地址,展示信息和图片,端口默认使用8080
在这里插入图片描述
电脑的IP通过cmd ipconfig查看:
在这里插入图片描述
在这里插入图片描述

2、Xml文件解析

(1)XML文件:

包含文件头(声明)、根节点、节点、内容、命名空间等内容
在这里插入图片描述
命名空间示意
在这里插入图片描述

(2)生成XML文件

  • 自己组拼
    在这里插入图片描述
    在这里插入图片描述

  • 使用Xml序列化器xmlserializer
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

(3)解析XML文件

在这里插入图片描述
在这里插入图片描述

(4)本文的news.xml文件:

<channel>
<item>
<title>军报评徐才厚</title>
<description>人死账不消反腐步不停,支持,威武,顶,有希望了。</description>
<image>http://192.168.1.127:8080/img/a.jpg</image>
<type>1</type>
<comment>163</comment>
</item>
<item>
<title>女司机翻车后直奔麻将室</title>
<description>女司机翻车后直奔麻将室,称大难不死手气必红</description>
<image>http://192.168.1.127:8080/img/b.jpg</image>
<type>2</type>
</item>
<item>
<title>小伙当“男公关”以为陪美女</title>
<description>来源:中国青年网,小伙当“男公关”以为陪美女,上工后被大妈吓怕</description>
<image>http://192.168.1.127:8080/img/c.jpg</image>
<type>3</type>
</item>
<item>
<title>男子看上女孩背影xxxx</title>
<description>来源:新京报,看到正脸后很惊喜</description>
<image>http://192.168.1.127:8080/img/d.jpg</image>
<type>1</type>
<comment>763</comment>
</item>
</channel>

3、ListView控件使用(Imageview,TextView)

ListView是可以垂直滚动的列表,可以展示多个条目

(1)ListView简单使用

  • 定义Listview:在布局中定义一个LIstView;单独建立条目的布局文件
  • 定义ListView的适配器
  • 实现适配器中的方法getcount、getview等

(2)ListView优化

  • Listview每次滑动到新的item,就会调用getview方法;如果不断创建view对象,会造成内存紧张;因此,通过converview实现复用:
    在这里插入图片描述

(3)ListView显示数据原理

在这里插入图片描述

(4)ListView一些小问题

在这里插入图片描述
View的宽高属性使用:包裹内容;展示多个条目时无法自动计算应展示的条目数,安卓系统自动多次校验,效率降低

(5)ListView显示复杂页面

在这里插入图片描述
在这里插入图片描述
此处就是需要将item的布局文件转换为View对象,采用View.inflate方法

在这里插入图片描述

4、网络编程

(1)HttpUrlConnection

控制网络收发数据:

			URL url=new URL("https://www.baidu.com");
//            创建httpurlconnection对象
            HttpURLConnection conn= (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            int code =conn.getResponseCode();

            if (code==200){
//                获取服务器返回的数据
                InputStream in=conn.getInputStream();
//                把流装换为字符串,抽出为工具类
                String content =StreamTools.readStream(in);
                tv_result.setText(content);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

(2)注意:安卓4.0以后联网操作只能在子线程

在实验过程中,在主线程里进行网络连接,报错:

android.os.NetworkOnMainThreadException

  • 主线程 :UI线程,onCreat方法所在的线程
  • 其他耗时的操作(联网,拷贝文件等)需要放在子线程中,否则可能会报“应用无响应”错误:ANR

(3)注意:安卓9.0以后联网强制使用https协议

这在正常访问网络时没有什么区别,但是本教程中使用tomcat在本地搭建了一下服务器进行访问,由于网站没有进行安全备案,使用https协议时出现错误:

CLEARTEXT communication not permitted by network security policy

有的请求库不会抛出这个错误,如果应用无故连接超时,抛出SocketTimeoutException,很可能也是这个原因引起的

  • 网上较简单的解决办法是通过在manifest的application标签中加入以下设置,来修改默认策略,这样就可以在安卓9.0中使用http协议
    <application
        android:usesCleartextTraffic="true"">
	</application>

5、Handler:消息机制

(1)用途:

  • 由于在子线程需要更新UI,(由于多个子线程之间的同步与互斥,比较复杂,google为了简化编程)安卓只允许在主线程中更新UI,因此采用消息机制

在子线程中更新UI:会报错:

W/System.err:> android.view.ViewRootImpl$CalledFromWrongThreadException:

网络连接

public class MainActivity extends AppCompatActivity {

    protected static final int REQUESTSUCCESS=0;
    protected static final int REQUESTNOTFOUND=1;
    protected static final int REQUESTEXCEPTION=2;
    private EditText et_path;
    private TextView tv_result;
    private Handler handler =new Handler(){
        //这个方法在主线程中执行
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case REQUESTSUCCESS:
                    String content=(String) msg.obj;
                    tv_result.setText(content);
                    break;
                case REQUESTNOTFOUND:
                    Toast.makeText(getApplicationContext(),"资源不存在",Toast.LENGTH_LONG).show();
                    break;
                case REQUESTEXCEPTION:
                    Toast.makeText(getApplicationContext(),"请求异常",Toast.LENGTH_LONG).show();
                    break;
                default:
                    break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_path =findViewById(R.id.et_path);
        tv_result =findViewById(R.id.tv_result);



    }
    public void myclick(View V){
        //创建子线程,在子线程中进行联网操作
        new Thread(){
            @Override
            public void run() {
                try {

                    String path=et_path.getText().toString().trim();
                    URL url=new URL("Https://"+path);
//            创建httpurlconnection对象
                    HttpURLConnection conn= (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(5000);
                    int code =conn.getResponseCode();

                    if (code==200){
//                获取服务器返回的数据
                        InputStream in=conn.getInputStream();
//                把流装换为字符串,抽出为工具类
                        String content =StreamTools.readStream(in);
//                        tv_result.setText(content);
//                        更新UI的操作,使用Hander实现
                        Message msg=Message.abtain();//可以减少对象的创建
                        msg.what=REQUESTSUCCESS;
                        msg.obj=content;
                        handler.sendMessage(msg);
                    }
                    else{
//                        请求不成功
                        Message msg=Message.abtain();//可以减少对象的创建
                        msg.what=REQUESTSUCCESS;
                        handler.sendMessage(msg);

                    }
                } catch (Exception e) {
                    e.printStackTrace();

                    Message msg=Message.abtain();//可以减少对象的创建
                    msg.what=REQUESTEXCEPTION;
                    handler.sendMessage(msg);
                }

            }
        }.start();

    }
}

(2)消息机制使用步骤

  1. 在主线程定义一个Handler private Handler handler=new Handler()
  2. 重写handler的里面的handlemessage方法
    public void handleMessage(android.os.Message msg){}
  3. 拿着我们在主线程创建的handler去子线程发消息handler.sendMessage(msg);
  4. handlemessage方法就会执行在这个方法里面去更新UI
  5. mesg可以携带任何数据:当handler需要处理多条不同消息时,msg.what可用于区分不同消息类型

(3)消息机制原理

在这里插入图片描述

  • 系统创建主线程时会创建消息循环器Looper
  • Looper中包含一个消息队列MessageQueue; Looper不停的监视消息队列,并从中取出message对象,
  • Looper.target为handler对象,Looper会调用handler的dispatchMessage方法,该方法会调用handleMessage方法;该方法就是我们创建handler对象时需要重写的方法、

(4)Handler其他API

  1. 延迟执行:Handler().postDelayed()
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                System.out.println("nihao");
            }
        },5000);

java中Timer可实现类似的功能(app退出后需要销毁Timer调度)

        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hahhhahah");
            }
        }, 5000,);

但是Timer子线程中无法更新UI,但是Handler的postDelayed方法可以

6、图片显示

(1)Imgview控件显示图片

  • 使用Bitmap显示图片:imgview对象setImageBitmap()方法
  • 使用bitmapFactory. decodeStream(inputStream in)
    将url打开的输入流转换为bitmap

(2)图片缓存

  1. 采用文件输出流,将url.openconnection()返回的输入流传入byte[] buffer后再写入文件
	File file=new File(getCacheDir(), Base64.encodeToString(path.getBytes(),Base64.URL_SAFE));
	InputStream in =conn.getInputStream();
	//缓存数据
	
	FileOutputStream fos=new FileOutputStream(file);
	int len=-1;
	byte[] buffer=new byte[1024];//1kb
	while((len=in.read(buffer))!=-1){
	    fos.write(buffer,0,len);
	}
	fos.close();
  1. 需要注意的是:new FileOutputStream(file)时file采用base64编码不会对"/"编码,因此可能导致无法找到文件;此时采用Base64.URL_SAFE选项
  2. 缓存逻辑:
  • 首次访问该图片根据path地址进行base64编码,将编码后的字符串作为文件名,将数据存入该文件中
  • 非首次访问该path,则直接看base64编码文件名的文件是否存在,存在则直接从文件获得bitmap
    总代码:
public class MainActivity extends AppCompatActivity {
    private TextView et_path;
    private ImageView iv;
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            iv.setImageBitmap((Bitmap)msg.obj);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_path=(TextView) findViewById(R.id.et_path);
        iv=(ImageView) findViewById(R.id.iv);

    }

    public void myclick(View v){

        new Thread(){
            @Override
            public void run() {
                try {
                    String path=et_path.getText().toString().trim();
                    File file=new File(getCacheDir(), Base64.encodeToString(path.getBytes(),Base64.URL_SAFE));

                    if (file.exists() && file.length()>0){
//                        如果文件存在,使用缓存数据
                        Bitmap bitmap= BitmapFactory.decodeFile(file.getAbsolutePath());
                        Message msg=Message.obtain();
                        msg.obj=bitmap;
                        handler.sendMessage(msg);
                    }
                    else{
//                        如果文件不存在,则连接网络
                        URL url=new URL(path);
                        HttpURLConnection conn=(HttpURLConnection) url.openConnection();
                        conn.setRequestMethod("GET");
                        conn.setConnectTimeout(5000);
                        int code=conn.getResponseCode();
                        //获取图片数据,
                        if (code==200){
                            InputStream in =conn.getInputStream();
                            //缓存数据

                            FileOutputStream fos=new FileOutputStream(file);
                            int len=-1;
                            byte[] buffer=new byte[1024];//1kb
                            while((len=in.read(buffer))!=-1){
                                fos.write(buffer,0,len);
                            }
                            fos.close();
                            in.close();

                            //将输入流转换为bitmap模式,BitmapFactory从各种资源
                            Bitmap bitmap= BitmapFactory.decodeFile(file.getAbsolutePath());
                            Message msg=Message.obtain();
                            msg.obj=bitmap;
                            handler.sendMessage(msg);
                        }

                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

(3)关于缓存目录

  • getCacheDir:在data/user/0/应用文件夹/cache:一般用来放不重要的缓存数据,系统有清理缓存的功能

在这里插入图片描述

  • getFileDir:重要的数据信息
    在这里插入图片描述

(4)开源项目SmartImageView

使用开源项目SmartImageView可以简化图片显示的代码,在本项目中使用了根据图片URL地址显示图片的方法

SmartImageView源码:https://github.com/loopj/android-smart-image-view
使用步骤:

  1. 下载源码,将loopj文件夹拷贝到源码com路径下
    在这里插入图片描述在这里插入图片描述
  2. 将原来布局文件的imageView改为com.loopj.android.image.SmartImageView(完整引用)
    在这里插入图片描述
  3. 代码中使用到该ImageView中的地方,改用SmartImageView
            SmartImageView iv_icon=(SmartImageView) view.findViewById(R.id.iv_icon);
            String imageUrl =newsList.get(position).getImage();
            iv_icon.setImageUrl(imageUrl);

7、更新UI的便捷API:runOnUiThread

由于在子线程里处理数据,然后更新UI——是十分常见的操作,因此提供了runOnUiThread()方法;其本质还是采用handler实现

使用方法:在子线程中直接使用runOnUiThread()方法,传入一个Runnable对象

以下代码:

Bitmap bitmap= BitmapFactory.decodeFile(file.getAbsolutePath());
Message msg=Message.obtain();
msg.obj=bitmap;
handler.sendMessage(msg);

可以替换为:

final Bitmap bitmap= BitmapFactory.decodeFile(file.getAbsolutePath());
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        iv.setImageBitmap(bitmap);
    }
});

二、项目实战:

1、功能:

  • 实现通过服务器地址取数据(xml文件);根据文件中包含的文字信息和图片源信息;拿到真实的数据;并展示

2、几个注意的点:

  1. 更新UI在在主线程,耗时操作在子线程
  2. 安卓9.0使用Http协议如何设置
  3. 总体布局文件中嵌套item布局
  4. ListView如何适配
  5. SmartImageView如何使用及原理

3、效果:

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章