安卓學習筆記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、效果:

在這裏插入圖片描述

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