簡體   English   中英

如何使用Socket.io從另一個類正確刷新ListView?

[英]How to properly refresh a ListView from another class by using Socket.io?

我試圖用socket.io為Android編寫一個簡單的聊天。
我的以下代碼可以工作,但是我不確定是否需要靜態方法,因為對於ListView字段和MyBaseAdapter類,我收到以下消息:

不要將Android上下文類放在靜態字段中; 這是內存泄漏

如果將其更改為非靜態,則會收到以下錯誤消息:

空對象引用上的MyBaseAdapter.notifyDataSetChanged()'

下面是我的代碼:

package com.example.seadog.fb_dialog;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;

import io.socket.emitter.Emitter;

public class MainActivity extends Activity implements Emitter.Listener {

    private static ArrayList arrayList = new ArrayList();

    private static ListView listView;
    private static MyBaseAdapter adapter;

    private TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        SocketIo socketIo = new SocketIo(this);

        if(socketIo.getSocket()==null) {
            socketIo.Connection();
        }

        listView = (ListView) findViewById(R.id.listView);
        adapter = new MyBaseAdapter(this, arrayList);
        listView.setAdapter(adapter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                Intent newActivity = new Intent(MainActivity.this, Conversation.class);
                startActivity(newActivity);

            }
        });

    }

    @Override
    public void call(Object... args) {

        adapter.notifyDataSetChanged();

    }

}

下面是我的SocketIo類代碼:

package com.example.seadog.fb_dialog;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.net.URISyntaxException;

import io.socket.client.Ack;
import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;

public class SocketIo {

    private static Socket mSocket = null;
    private Emitter.Listener messageListener;

    private String API_BASE_URL = "http://10.0.2.2:3030";

    private Integer id = 654864;     // Website ID
    private Integer userId = 6522;   // UserID

    private String jwt;

    public SocketIo(Emitter.Listener messageListener) {
        this.messageListener = messageListener;
    }

    public void Connection() {

        try {
            mSocket = IO.socket(API_BASE_URL);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        mSocket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {

            /*
             * Join to default a room after connect
             */

            @Override
            public void call(Object... args) {

                // create json object and join to room (Website ID)
                // ...more code, not important here

            }

        }).on("jwt", new Emitter.Listener() {

            /*
             * Get a token and find all active conversations
             */

            @Override
            public void call(Object... args) {

                // get a token as args
                jwt = args[0].toString();

                JSONObject jsonObject = new JSONObject();

                try {
                    jsonObject.put("token", jwt);
                } catch (JSONException e) {
                    e.printStackTrace();
                }

                // find active conversations
                mSocket.emit("messages::find", jsonObject, new Ack() {
                    @Override
                    public void call(Object... args) {

                        String response = args[args.length - 1].toString();

                        try {

                            JSONArray jsonArray = new JSONArray(response);

                            for (int i = jsonArray.length() - 1; i >= 0; i--) {

                                // create JSON object and add to arrayList

                                mainActivity.setArrayList(ld);

                            }

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

                    }
                });

            }

        }).on("message", new Emitter.Listener() {

            /*
             * Message Listener
             */

            @Override
            public void call(Object... args) {

                String message = null;

                try {
                    JSONObject object = (JSONObject) args[0];

                    // Create here JSON Object and add to JSON Array

                    mainActivity.setArrayList(ld);

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

                String finalMessage = message;

                //mainActivity.notifyDataSetChanged();

            }

        }).on(Socket.EVENT_DISCONNECT, new Emitter.Listener() {

            @Override
            public void call(Object... args) {
            }

        });

        mSocket.connect();

    }

    public Integer getId(){
        return id;
    }

    public Socket getSocket(){
        return mSocket;
    }

}

將您的代碼分解為MVP模式。 編寫與您的活動直接相關的演示者。 Presenter用於包裝對I / O,數據庫等所需的對庫,框架等的調用。

演示者可以填寫一個界面

void onData(ListData data);

與其直接致電您的活動,不如致電演示者。 他將緩存您的數據,並根據您的Activity的需要對其進行轉換,並且我們的Activity可能具有通過類似以下接口公開的功能

void displayListItems(List<ListDataViewModel> viewModels);

如果將關閉“活動/演示者”,請嘗試實現一個存儲庫,該存儲庫將數據存儲到本地數據庫中,以便您可以還原它們。

首先,將所有內容更改回非靜態

使您的Activity實現Emitter.Listener (或自己定義一個類似性質的接口)

將該接口作為參數傳遞給SocketIO類(假設您可以對其進行編輯)

在必須實現的調用方法中,您可以調用非靜態方法來更新列表

假設您在SocketIO類中implements Emitter.Listener ,則將您放置的偵聽器用作消息操作的參數

.on("message", this.messageListener) 

首先,向SocketIO添加參數

private Emitter.Listener messageListener;
public SocketIO(Emitter.Listener messageListener) {
    this.messageListener = messageListener;
}

然后,更新您的活動

public class MainActivity extends Activity implements Emitter.Listener {

    private ArrayList arrayList = new ArrayList();

    private ListView listView;
    private MyBaseAdapter adapter;

    private TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SocketIo socketIo = new SocketIo(this);  // Pass the interface parameter
        adapter = new MyBaseAdapter(this, arrayList);

        ...
    }

    @Override
    public void call(Object... args) {

       ... // TODO: parse out args

       adapter.notifyDataSetChanged();
    }

如果您改為定義自己的界面,

.on("message", new Emitter.Listener() {
    @Override
    public void call(Object... args) {

        String message = null;
        //  TODO parse your message... 

        // don't update the adapter here, do it where you implement this interface 
        if (messageListener != null) {
            messageListener.onMessageReceived(message);
        } 
    }

當然,請選擇自己的命名約定和變量類型

解決此問題的簡單方法是使用在主(UI)線程上創建的處理程序。 將引用傳遞到您的Emitter.Listener偵聽器中,當您想要更新ListView時,可以將在偵聽器中收到的數據打包到Message中 ,並將其發送給您在主(UI)上創建的處理程序接收線。 當您從處理程序的handleMessage()中接收數據時,您將從Message對象中提取數據,該方法將其作為參數並更新適配器,然后調用notifyDataSetChanged,當接收到數據時,該通知數據將始終在UI線程上被調用。

(有趣的是,整個Android SDK使用Handler / Message系統來異步執行工作,然后將結果傳遞給主(UI)線程)。

絕對不要創建對活動,片段,視圖的靜態引用,因為它們可以保留對擁有活動的引用,並且無意間將整個活動及其所有視圖元素無害地泄漏為單個旋轉情況。 例如,您的ListView對象是一個view元素,它從其自身的活動內部保留在上下文中,android系統旋轉一圈會破壞該活動並嘗試對其進行GC,但由於無法訪問的對象持有對該活動的引用,因此不能。

package com.example.seadog.fb_dialog;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.os.Handler;
import android.os.Message;

import java.util.ArrayList;

public class MainActivity extends Activity implements Handler.Callback {

    private ArrayList arrayList = new ArrayList();

    private ListView listView;
    private MyBaseAdapter adapter;

    private TextView textView;
    private Handler mHandler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Pass in a reference to ourself so that we can get messages on the UI thread in the handleMessage below.
        // Since onCreate is called on the main (UI) thread, the handler will also get created on the same thread.
        mHandler = new Handler(this);

        SocketIo socketIo = new SocketIo();

        if(socketIo.getSocket()==null) {
            socketIo.Connection();
        }

        // Pass in a reference to our handler.
        socketIo.on("message", new MyEmitter(mHandler));

        listView = (ListView) findViewById(R.id.listView);
        adapter = new MyBaseAdapter(this, arrayList);
        listView.setAdapter(adapter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                Intent newActivity = new Intent(MainActivity.this, Conversation.class);
                startActivity(newActivity);

            }
        });
    }
    @Override
    public boolean handleMessage(Message msg) {
        // THIS GETS CALLED ON THE MAIN (UI) THREAD.
        // Since you are only sending once Message at a time you don't have to worry about handling others.
        Bundle bndl = msg.getData();
        ListData data = bndl.getParcelable("ListData");     
        // I assume this method exist on your adapter class if not, use what every method you want to add a new set of data.
        adapter.addListData(data);
        // At this point you just need to tell the adapter that it's dataset has changed and should update the listview.

        adapter.notifyDataSetChanged();     
        return true;
    }


    private static class MyEmitter extends Emitter.Listener {

        private WeakReference<Handler> mHandler;
        public MyEmitter(Handler handler) {
            mHandler = new WeakReference<Handler>(handler);
        }

        public void call(Object.. args) {
            Handler handler = mHandler.get();
            // The handler is stored in a week reference so that this listener doesn't
            // improperly hold on to it and prevent it from getting GC'd.
            if(handler != null) {               
                try {
                    JSONObject object = (JSONObject) args[0];
                    String message = null; = object.getString("message");
                    // You will need to make this class parcelable.. I deliberately left this out.
                    ListData ld = new ListData();
                    ld.setID(1);
                    ld.setTitle("Klient:");
                    ld.setDescription(message);                 
                    // Get a message object that will be sent to the activities handler.
                    Message msg = handler.obtainMessage();
                    Bundle bndl msg.getData();
                    bndl.putParcelable("ListData", ld);
                    // This will send the message to the activities handler on the UI thread. The handleMessage will get called.
                    msg.sendToTarget();

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

這段代碼需要做一些小的改動,但是總的思路就在那里。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM