I am trying to implement MUC with aSmack, but seem to be unable to update the UI with the ArrayAdapter correctly.
I have set up my listener in my ChatService.java like this:
connection.addPacketListener(new MessageListener(), new PacketFilter() {
public boolean accept(Packet packet) {
Message msg = (Message) packet;
return msg.getBody() != null;
}});
And my listener class looks like this:
private class MessageListener implements PacketListener {
/**
* Constructor.
*/
public MessageListener() {
}
public void processPacket(Packet packet) {
if(packet instanceof Message ) {
Message message = (Message) packet;
android.os.Message aosMessage = new android.os.Message();
aosMessage.what = message.getType().ordinal();
aosMessage.obj = message;
messageHandler.handleMessage(aosMessage);
}
}
}
Now the problem is as follows;
First, I tried updating the UI via the handler (the handler is defined in my ChatActivity.java):
private final Handler messageHandler = new Handler() {
private Message chatMessage;
#Override
public void handleMessage(final android.os.Message msg) {
chatMessage = (Message) msg.obj;
if(Message.Type.values()[msg.what].equals(Message.Type.groupchat)) {
conversationArrayAdapter.add("Received group message: " + chatMessage.getBody());
} else if (Message.Type.values()[msg.what].equals(Message.Type.chat)) {
conversationArrayAdapter.add("Received private message: " + chatMessage.getBody());
}
}
};
But this won't update the UI until an interaction is introduced from the user's end.
After a little web-digging, I realized I need to use the post (or runonUIthread) method to implement this, and so I tried:
private final Handler messageHandler = new Handler() {
private Message chatMessage;
#Override
public void handleMessage(final android.os.Message msg) {
chatMessage = (Message) msg.obj;
this.post(new Runnable() {
public void run() {
if(Message.Type.values()[msg.what].equals(Message.Type.groupchat)) {
conversationArrayAdapter.add("Received group message: " + chatMessage.getBody());
conversationArrayAdapter.notifyDataSetChanged();
} else if (Message.Type.values()[msg.what].equals(Message.Type.chat)) {
conversationArrayAdapter.add("Received private message: " + chatMessage.getBody());
conversationArrayAdapter.notifyDataSetChanged();
}
}
});
}
};
But now the run() method is called multiple times (confirmed on debug), and the UI is updated with a seemingly-random-amount of the same messages (different amount of repetitions for every message, but all the messages are shown).
This is my first android app (but I am a java developer by trade), and I am quite sure I am doing something wrong in regards to the architecture here. Any assistance (preferably a detailed one - with code samples and / or references to the correct areas of the documentation) would be greatly appreciated.
Thanks in advance!
Corrected working snippet after #Emil's answer
private class MessageListener implements PacketListener {
/**
* Constructor.
*/
public MessageListener() {
}
public void processPacket(Packet packet) {
if(packet instanceof Message ) {
Message message = (Message) packet;
android.os.Message aosMessage = new android.os.Message();
aosMessage.what = message.getType().ordinal();
aosMessage.obj = message;
aosMessage.setTarget(messageHandler);
aosMessage.sendToTarget();
}
}
}
Don't call handleMessage directly :
messageHandler.handleMessage(aosMessage);
You should obtain a message from a given Handler or setTarget(Handler handler) on a message and then call sendToTarget() on the message to get it to be sent and processed by the Handler. If you implement the Handler correctly you don't need the post for this.
Related
I'm trying to use this article to create asynchronous UDP socket.
So I've this code:
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpThread
extends HandlerThread {
private static final String TAG = "UDP";
private final Handler uiHandler, workerHandler;
private final DatagramSocket socket = new DatagramSocket();
public UdpThread(final Handler uiHandler, final String hostname, final int port) throws SocketException {
super(TAG);
this.uiHandler = uiHandler;
start();
workerHandler = new Handler(getLooper(), new Handler.Callback() {
#Override
public boolean handleMessage(final Message msg) {
/*
if (msg.what == port && msg.obj == hostname) {
final InetSocketAddress address = new InetSocketAddress(hostname, port);
Log.d(TAG, "Connecting to " + address);
try {
socket.connect(address);
} catch (SocketException se) {
throw new RuntimeException(se);
}
}
*/
msg.recycle(); //java.lang.IllegalStateException: This message cannot be recycled because it is still in use.
return true;
}
});
workerHandler.obtainMessage(port, hostname).sendToTarget();
}
}
But when I run the code, I get the mentioned java.lang.IllegalStateException: This message cannot be recycled because it is still in use. when trying to recycle the message. Why is that and how to solve it and prevent memory leaks?
Well first of all lets see how Message recycle() method works.
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
So you are getting IllegalStateException if it is in use
isInUse() just checks flag and looks like:
boolean isInUse() {
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
}
And when we try to read about that flag we see description:
If set message is in use.
This flag is set when the message is enqueued and remains set while it
is delivered and afterwards when it is recycled. The flag is only
cleared when a new message is created or obtained since that is the
only time that applications are allowed to modify the contents of the
message.
It is an error to attempt to enqueue or recycle a message that is
already in use.
So what we have
You cant recycle message until its "in use"
It is "in use" until new message obtained or created
How to solve the problem
There is method recycleUnchecked() inside Message class to recycle message object even if it is in use.Thats what you need! Description of it:
Recycles a Message that may be in-use.
Used internally by the MessageQueue and Looper when disposing of
queued Messages.
Worst thing that it uses internally and has package access. Good thing that it uses internally when you call:
handler.removeMessages(int what)
So I guess final solution is:
replace
msg.recycle();
to
try {
msg.recycle(); //it can work in some situations
} catch (IllegalStateException e) {
workerHandler.removeMessages(msg.what); //if recycle doesnt work we do it manually
}
You shouldn't call msg.recycle() yourself, message is recycled automatically by Looper after it has been dispatched/processed (after your handleMessage() returns), see source code.
Try to use AsyncTask to delete the message when the handler thread finish to proceed it.
//[..]
//synchronized with the handler thread
#Override
public boolean handleMessage(final Message msg) {
new MessageDestructor().execute(msg);
return true;
}
//[..]
private class MessageDestructor extends AsyncTask<Message, Void, Void> {
Message msg;
#Override
protected String doInBackground(Message... params) {
msg = (Message) params[0];
return null;
}
#Override
protected void onPostExecute(Void result) {
msg.recycle(); //synchronized with the main thread
}
#Override
protected void onPreExecute() {
}
#Override
protected void onProgressUpdate(Void... values) {
}
}
here is my hander:
public Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String mmsg = msg.getData().getByteArray("msg").toString();
Toast.makeText(clientActivity.this, mmsg,Toast.LENGTH_LONG).show();
}
};
I send data from separate thread:
public class client implements Runnable
{
private void showtoast(byte[] msgtoshow){
try {
Bundle mbundle = new Bundle();
Message greeting = new Message();
mbundle.putByteArray("msg", msgtoshow);
greeting.setData(mbundle);
handler.sendMessage(greeting);
}catch(Exception e){
Log.d("error",e.getMessage());
}
}
public void run()
{
msgstrng = "this is supposed to be some text";
showtoast(msgstrng.getBytes());
}
}
Instead of line that I send in msgstng I toast some [B#411dd11] which is always different. I guess it's timestamp or smth. how to get that String value msgstng?
Actually more important for me is to get bytes array, as I'll send bitmaps from socket to UI if i learn this issue
The problem seems to be the way you are getting the string in your Handler.
Try something like this:
String mmsg = new String(msg.getData().getByteArray("msg"));
I made a simple RPC mechanism apps for android and I faced a problem that I can not go back to the UI Thread from RPC class.
Basically I have 3 classes(ServerActivity,ServerView and ServiceImplementation), I created 3 classes because I use RPC and Protocol Buffer for drawing.
Server Activity :
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_sv = new ServerView(this);
setContentView(_sv);
rpcConnectionFactory = SocketRpcConnectionFactories.createServerRpcConnectionFactory(SERVER_PORT);
int nThreadPool = 1;
server = new RpcServer(rpcConnectionFactory, Executors.newFixedThreadPool(nThreadPool), true);
server.registerBlockingService(Service.newReflectiveBlockingService(new ServiceImpl(myServiceHandler)));
server.run();
}
Handler myServiceHandler = new Handler() {
public void handleMessage(Message msg) {
Log.i("Handler", "Handler IN");
_sv.set(msg.what); /*To communicate with the view*/
super.handleMessage(msg);
}
};
ServiceImplementation :
public CanvasServiceImpl(Handler mActivity) {
backToUIThread = mActivity;
}
public Response drawCircle(RpcController controller, Circle1 request)
throws ServiceException {
android.os.Message message = new android.os.Message();
message.what = 1;
ImplHandler.sendMessage(message);
Response response = Response.newBuilder().setResult("drawCircle Success").build();
return response;
}
I can not reach my UI Thread. Does anybody know why ?
Thanks,
Robert
Instead of
ImplHandler.sendMessage(message);
use
backToUIThread.sendMessage(message);
I seem to be having trouble with updating a TextView from a thread. I have a GameConnection class (which manages a socket connection) which I want to use across activities. It calls a local "onMessage", which then uses the target handler to call dispatch Message. The "Handler" in this case, is in my GameBrowser activity.
Here's code from the GameConnection class.
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
String message = "".intern();
// as a newline character is read, we interpret it as a message
while ((message = in.readLine()) != null && isConnected){
onMessage(message);
}
As said above, a local method "onMessage" method handles dispatching of the message.
private void onMessage(String message){
... // create message from String
handler.dispatchMessage( msg );
}
However, when I get the response in the GameBrowser class, I get a CalledFromWrongThreadException . Initially, I was using a callback method, which of course wasn't working. So, after some research, I've found that I have to use a Handler, but I can't seem to get it right.
public class GameBrowser extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(C.tag, "GameBrowser.onCreate addr:" + this);
handler = new Handler(new HandlerCallback());
connection.addMessageListener(handler);
connection.connect();
txtGameLabel = (TextView)findViewById( R.id.txtGamesLabel);
setContentView(R.layout.game_browser);
}
private class HandlerCallback implements Callback{
#Override
public boolean handleMessage(Message msg) {
if (txtGameLabel == null){
txtGameLabel = (TextView)findViewById( R.id.txtGamesLabel);
}
String message = msg.getData().getString("message");
Log.d(C.tag, "GameBrowser recieved message " + message);
txtGameLabel.setText("Data: " + message);
return true;
}
}
}
I figured out what I was doing wrong. Instead of calling the handler from the socket thread, I used a callback, then used Runnable to post to the handler in the GameConnection class. When onMessage executes "run", which executes "updateTextField", we're back in the main thread.
#Override
public void onMessage(final String message) {
handler.post(new Runnable(){
#Override
public void run() {
updateTextField(message);
}
});
}
private void updateTextField(String message){
if (txtGameLabel == null)
txtGameLabel = (TextView)findViewById( R.id.txtGamesLabel);
txtGameLabel.setText(message);
}
In Handler, we can pass some data from a background thread to the UI thread like this:
private void someBackgroundThreadOperation() {
final String data = "hello";
handler.post(new Runnable() {
public void run() {
Log.d(TAG, "Message from bg thread: " + data);
}
}
}
If we use the above, we cannot then use Handler.removeCallbacks(Runnable r), because we won't have references to any of the anonymous runnables we created above.
We could create a single Runnable instance, and post that to the handler, but it won't allow us to pass any data through:
private void someBackgroundThreadOperation() {
String data = "hello";
handler.post(mRunnable); // can't pass 'data' through.
}
private Runnable mRunnable = new Runnable() {
public void run() {
Log.d(TAG, "What data?");
}
}
We can however use the Handler.removeCallbacks(mRunnable) method then, since we have a reference to it.
I know I can setup some synchronization myself to get the second method working, but I'm wondering if Handler offers any utility for passing data through to a single referenced Runnable, almost like in the example above?
The closest I can think of from browsing the docs is to use something like:
private void someUiThreadSetup() {
mHandler = new Handler(new Callback() {
#Override
public boolean handleMessage(Message msg) {
String data = (String)msg.obj;
return false;
}
});
}
private void someBackgroundThreadOperation() {
String data = "please work";
Message msg = mHandler.obtain(0, data);
mHandler.sendMessage(msg);
}
private void cleanup() {
mHandler.removeMessages(0);
}
Is this the proper way of doing it?
Thanks
IMHO, the pattern you seek is packaged as the AsyncTask class.