如何使用Socket.io从另一个类正确刷新ListView?

时间:2017-05-24 13:44:19

标签: java android listview nullpointerexception 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;
    }

}

3 个答案:

答案 0 :(得分:1)

将代码分解为MVP模式。 写一个与您的活动直接相关的演示者。 Presenter用于包含对I / O,数据库等所需的库,框架等的调用。

Presenter可能会填满界面

void onData(ListData data);

不要直接调用您的活动,请致电您的演示者。他将缓存您的数据,根据您的活动需要对其进行转换,我们的活动可能有一个功能,通过

等界面公开
void displayListItems(List<ListDataViewModel> viewModels);

如果要关闭活动/演示者,请尝试实施一个存储库,即将数据存储到本地数据库,以便您可以恢复它们。

答案 1 :(得分:0)

首先,将所有内容更改回非静态

让您的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);
        } 
    }

当然,选择自己的命名约定和变量类型

答案 2 :(得分:0)

此问题的一个简单解决方案是使用您在主(UI)线程上创建的Handler。将引用传递给您的Emitter.Listener侦听器,当您希望更新ListView时,可以将收听的数据打包到Message并发送给HandlerhandleMessage()。您在主(UI)线程上创建的。当您在处理程序Message中收到数据时,您从https://developers.google.com/drive/android/examples/对象中提取数据,该方法将其作为参数并更新您的适配器,然后调用始终在UI上调用的notifyDataSetChanged收到数据时的线程。

(有趣的是,整个Android SDK使用Handler / Message系统来异步执行工作,然后将结果传递给主(UI)线程。)

绝对不要创建对活动,片段和视图的静态引用,因为它们可以保存对拥有活动的引用,并且无意中将整个活动及其所有视图元素泄漏到一个无关紧要的情况下作为单个循环。例如,你的ListView对象是一个视图元素,内部从其拥有的活动中保存到上下文中,一个旋转,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();
                }
            }
        }   
    }
}

这段代码需要一些小改动,但总体思路就在那里。