I have an android app that receives values from a Bluetooth device. Everything works smooth and nice the first time I open the Activity. The value from the Bluetooth is shown in a TextView.
The app consists of two activities --> Main and Details(details includes a few fragments).
In Main activity, the user enters an ID and presses okay button. Then I start the Bluetooth thread and directly connect to a Bluetooth device using the MAC address. And then I open the Details activity.
In Details activity, I've made a Handler class that puts the value from the Bluetooth device to the text view. Then the users has yes and no buttons that save the value (or don't) in the database, when pressed and he is redirected back to the Main activity.
The first time this happens, everything works great. But the second time the Details activity is opened, the handle doesn't update the UI. I cannot understand why, if I try to debug, I can see that it works as it should -> the handler gets the value and updates the text views, but for some reason I can't see them in the UI.
Does anybody have any ideas what the problem might be?
The Handle looks like that:
static class MyHandler extends Handler {
private final WeakReference<DetailsFragment> mFragment;
public MyHandler(DetailsFragment fragment) {
mFragment = new WeakReference<>(fragment);
}
#Override
public void handleMessage(Message msg) {
DetailsFragment fragment = mFragment.get();
if (fragment != null) {
String tempValue = (String) msg.obj;
if (tempValue != null && !tempValue.equals("")) {
fragment.updateTextViewValue(tempValue.trim());
}
}
}
}
This is cancel button click in the Details activity:
public void cancelButtonClick() {
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
getActivity().finish();
}
This is the Bluetooth ConnectedThread:
public class ConnectedThread extends Thread {
private final int INCOMMING = 1;
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private final Handler mHandler;
public ConnectedThread(BluetoothSocket socket, Handler handler) {
mmSocket = socket;
mHandler = handler;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams using temp objects, because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
//log exception
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the streams
int bytes = 0; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
buffer[bytes] = (byte) mmInStream.read();
if ((buffer[bytes] == '\n') || (buffer[bytes] == '\r')) {
String value = new String(buffer, 0, bytes);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(INCOMMING, value).sendToTarget();
bytes = 0;
} else
bytes++;
} catch (IOException ex) {
//log the exception
break;
}
}
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
// log the exception
}
}
}
I am wondering if maybe when I open the Details activity for second time, the ConnectedThread and Handler are updating the old TextView, but is that possible, since I am using a WeakReference ?
Your description makes me think you may have a leaked activity, your thread would then update a fragment which is not displayed anymore.
You may try to launch the following command line:
adb shell dumpsys meminfo <your.app.package>
You will find a good explanation of the result here.
What is interesting for you is found in the "Objects" section, where you will find an "Activities" number.
You may go that your Details activity, come back to the Main one, go again in Details, etc...
The activites number should stay stable. If it increases, you may have a leak on your activity.
From there, you should look for retained views / fragments (I could see you had a WeakReference in your handler, so you know how to fix it)
Here, the profiling tools such as the Memory Monitor and the Allocation Tracker will surely help, but they take some time fto be understood.
Assuming that you don't actually update the Handler of the ConnectedThread, the question isn't "why doesn't the UI update?" but rather, "how could the UI update?"
You'll need to update the thread's mHandler member:
public void setHandler( MyHandler newHandler ) {
mHandler = newHandler;
}
and then add
connectedThread.setHandler( new MyHandler( this ) );
to your fragment's onActivityCreated() event.
Depending on how much traffic you have coming across, an event bus might be a better implementation.
Okay, I think I got it right, I just had to cancel the ConnectThread and ConnectedThread when I redirect back to the MainActivity. This fixed my problem. Embarrassing, but I am still a beginner.
However, I guess I still have a problem with the leaked activities. But that's another topic that I'll continue fighting with.
Related
I have written a menu for an application with multiple buttons. Two of these buttons trigger two separate Bluetooth methods. The problem is that on multiple quick presses of these buttons the app crashes because each method is attempting to manage the Bluetooth connection (while another may close that connection). I have tried setting a variable 'true' while any of the methods is running and checking for that but it does not work. I am unsure of whether the system runs each method concurrently in different threads or if it enqueues the methods .
The question is how exactly do I stop a button press to run a method while another method is executing? I don't need it enqueued after the executing one finishes, I only need it blocked.
EDIT: Added code of one of the methods below, as requested (the other one is identical, with the exception of two strings, which are irrelevant in this context):
public void lock(View button_lock) {
if(ok)
return;
if (btAdapter == null) {
Context context = getApplicationContext();
CharSequence text = "Bluetooth not supported!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return;
}
else if (address == null) {
Context context = getApplicationContext();
CharSequence text = "Please pair your phone with SmartLock.";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return;
}
if (!btAdapter.isEnabled()) {
btAdapter.enable();
ok=true;
}
mHandler.postDelayed(new Runnable() {public void run() {
BluetoothDevice device = btAdapter.getRemoteDevice(address);
ParcelUuid[] uuids = device.getUuids();
BluetoothSocket mmSocket;
try {
mmSocket = device.createRfcommSocketToServiceRecord(uuids[0].getUuid());
mmSocket.connect();
OutputStream out = mmSocket.getOutputStream();
InputStream in = mmSocket.getInputStream();
out.write("1".getBytes());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
in.close();
out.close();
mmSocket.close();
in = null;
out = null;
mmSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}}, 1000);
Context context = getApplicationContext();
CharSequence text = "Bike locked!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
mHandler.postDelayed(new Runnable() {public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
btAdapter.disable();
ok=false;
}}, 2000);
}
As you already tried it with a boolean, a semaphore could do the trick here.
You can't solve this problem by disabling the button in onClick(), because of the way Android's event system works. When the user presses the button, a "click" event is queued to the event queue. If the user presses the button twice in rapid succession (especially on low-end devices or when the UI thread is busy), 2 "click" events will be inserted into the queue. You cannot prevent this.
What you need to do is to remember that you've processed the "click" event and ignore any that arrive after that (until you want to allow the user to click again). It sounds like you have already tried this. Please post your code so we can see what is wrong.
After seeing your code I have the following input:
If mHandler has been created on the main (UI) thread, you have a problem You have code here that is doing network I/O and sleeping. You absolutely can not do that on the main (UI) thread. Make sure this stuff is running in a background thread. To do this, either create your own Thread or make sure that your Handler is created on a background thread (see HandlerThread as one example).
Before you call postDelayed(), set a boolean variable running to true. This flag should be cleared to false when the posted Runnable completes. To make sure of this, wrap your whole run() method in a try/catch and clear the variable in a finally block.
In lock(), first check if the boolean variable running is true. If it is, you should just return immediately, which will ignore click events that occur when you aren't ready for them.
Try this:
bluetoothMethod();
bluetoothButton.setEnabled(false);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
bluetoothButton.setEnabled(true);
}
}, duration);
(duration in milis)
I've implemented this tutorial on my APP, but I did many changes.... I've created a TabLayout so what I did (I don't think that's the good idea, well it is not since it doesn't work :)) on each fragment I copy pasted the code of the tutorial (I created the sockets to connect to my Bluetooth, I create connection to the device...) and when I tested it only with one Activity it worked well... but when I added the TabLayout it started to don't work. I think I could do all of the code of the Bluetooth on the Activity and then work with the objects of that Activity (from the Fragment I mean...) the problem is that on onPause() I have this :
#Override
public void onPause() {
super.onPause();
Toast.makeText(getActivity(), "onPause", Toast.LENGTH_SHORT).show();
try {
btSocket.close();
} catch (IOException e2) {
}
}
And everytime I use this :
private void startVoiceRecognitionActivity(){
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.VoiceControllerText));
startActivityForResult(intent, REQUEST_CODE);
}
It enters on onPause() and then the socket is closed, and I can't send info to Bluetooth I tried to comment that line btSocket.close(); but the error says that socket is closed, I didn't commented the line of the other Tab (I only have 2) should I comment also the socket.close() of the other Tab?....
I'm looking for a solution that helps to me to implement / guide how to implement all of the code of Bluetooth as another class or something, that if I enter on the onPause() from one Tab the socket doesn't close..
And by the way I'm not sure that copying pasting the code of the Bluetooth (They are the same in one Fragment than the other....) it's a good idea... same UUID same all...
If you guys need more code to check it out, let me know and I'll post it.
Thanks.
EDIT
First I have the first Activity which I sent to the MainActivity the MAC address as follows :
Intent i = new Intent(DeviceListActivity.this, MainActivity.class);
i.putExtra(EXTRA_DEVICE_ADDRESS, address);
i.putExtra("name", name);
startActivity(i);
This is the most important code of my DeviceListActivity...
The second thing that I have is MainActivity but there I don't have anything about Bluetooth because I do stuff with it on Fragments inside of it...
I have this Fragment which works perfect (it's the first one) :
Atributes
//Sending info
Handler bluetoothIn;
private ConnectedThread mConnectedThread;
final int handlerState = 0; //used to identify handler message
private BluetoothAdapter btAdapter = null;
private BluetoothSocket btSocket = null;
// SPP UUID service - this should work for most devices
private static final UUID BTMODULEUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// String for MAC address
private static String address="";
in onCreate() I call this :
btAdapter = BluetoothAdapter.getDefaultAdapter();// get Bluetooth adapter
if (btAdapter == null) {
Toast.makeText(getActivity(), getString(R.string.BtNotSupported), Toast.LENGTH_SHORT).show();
}
checkBTState();
I have the method to create the socket
private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
return device.createRfcommSocketToServiceRecord(BTMODULEUUID);
}
This is my onResume()
#Override
public void onResume() {
super.onResume();
//Get MAC del intent
Intent intent = getActivity().getIntent();
address = intent.getStringExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
//Creates a device with the MAC from DeviceListActivity
if(btAdapter!=null) {
BluetoothDevice device = btAdapter.getRemoteDevice(address);
try {
btSocket = createBluetoothSocket(device);
} catch (IOException e) {
ShowSnack(getString(R.string.SocketCreationFailed), Color.RED);
}
//Trying to connect
try {
btSocket.connect();
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
}
}
mConnectedThread = new ConnectedThread(btSocket);
mConnectedThread.start();
}
else{
ShowSnack(getString(R.string.toast_bt_unavailable), Color.RED);
}
}
This is my onPause()
#Override
public void onPause() {
super.onPause();
try {
//Close socket if leaves the Activity
btSocket.close();
} catch (IOException e2) {
}
}
And this is the method that I call to see if Bluetooth is enabled or not.
private void checkBTState() {
if (btAdapter == null) {
ShowSnack(getString(R.string.toast_bt_unavailable), Color.RED);
} else {
if (btAdapter.isEnabled()) {
} else {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 1);
}
}
}
This is my ConnectedThread class to send and recieve stuff from Bluetooth.
private class ConnectedThread extends Thread {
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
InputStream tmpIn = null;
OutputStream tmpOut = null;
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[256];
int bytes;
while (true) {
try {
bytes = mmInStream.read(buffer); //read bytes from input buffer
String readMessage = new String(buffer, 0, bytes);
bluetoothIn.obtainMessage(handlerState, bytes, -1, readMessage).sendToTarget();
} catch (IOException e) {
break;
}
}
}
//Send stuff to Bluetooth
public void write(char input) {
try {
mmOutStream.write(input);
} catch (IOException e) {
}
}
}
Well and now, when I'm having problems is on the second Fragment where I have THE SAME code as here... that's why I guess that crashes when trying to use Voice recognision... when I try to send something to Bluetooth, well.. I'm sorry if that's too much code but that's the only thing that I have hope you understand my problem.
One problem that you're facing is that it seems that you're trying to manage the lifecycle of your Bluetooth connection all from within your activity. As you've seen, this can cause problems when the Activity's lifecycle functions (such as onPause() and onResume()) don't perfectly align with the lifetime of your connection. To solve this, you can create a Service that handles all of your connecting, sending and receiving, and disconnecting from that Bluetooth connection. The Service's lifetime is independent from the Activity, so even if your user is switching between Activities and Fragments, you can keep the Bluetooth connection open.
To set up your Service, make a new class that extends Service and put all of your Bluetooth handling objects in it.
public class BluetoothService extends Service {
public static final String BLUETOOTH_SERIAL_UUID = "00001101-0000-1000-8000-00805F9B34FB";
private BluetoothSocket mSocket;
private String mAddress = "bluetooth_mac_address_here";
public void onCreate() {
//Set up Bluetooth socket.
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
if(btAdapter.isEnabled()) {
BluetoothDevice btDevice = btAdapter.getRemoteDevice(mAddress);
mSocket = btDevice.createRfcommSocketToServiceRecord(BLUETOOTH_SERIAL_UUID);
btAdapter.cancelDiscovery();
mSocket.connect();
}
}
}
This sets up the mSocket object when the Service is first launched. After that point, you'll be able to interact with the remote bluetooth device by simple calls to mSocket.getInputStream() and mSocket.getOutputStream() and reading/writing data using those. However, if you're not familiar with using Services, it can be a little confusing as to how to get your data from the Activity to and from the Service to transfer your data. Here's a way to do it using Intents.
Inside the same BluetoothService class, override onStartCommand():
public class BluetoothService extends Service {
...
public static final String ACTION_SEND_DATA = "send_data";
public static final String ACTION_RECEIVED_DATA = "received_data";
public static final String EXTRA_BLUETOOTH_DATA = "bluetooth_data";
public int onStartCommand(Intent intent, int flags, int startId) {
//Register a BroadcastReceiver to handle "send" requests.
LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//Parse your data to send from the intent.
if(intent.getAction().equals(ACTION_SEND_DATA)) {
byte[] data = intent.getByteArrayExtra(EXTRA_BLUETOOTH_DATA);
//Send the data over the Bluetooth Socket.
try {
mSocket.getOutputStream().write(data);
} catch(IOException ioe) {
//This might happen if you try to write to a closed connection.
ioe.printStackTrace();
}
}
}
return Service.START_STICKY;
}
}
This will give you a way to use Intents to send your data from an Activity to the Service, but not yet to receive that data. I'll get to that later. Note that I've used LocalBroadcastReceiver to register the intent. This means that the BroadcastReceiver that we register will only be given intents that were both broadcast from within your app and have a matching action. I just used that to simplify the intent interactions, but in the future if you want to allow external apps to send data using your service (probably unlikely), then you'll need to change that. Anyway, from your Activity, do the following to send the data through your Service:
public class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
...
String myString = "This is some data I want to send!";
//Create an intent with action saying to send data
//with the byte[] of data you want to send as an extra.
Intent sendIntent = new Intent(BluetoothService.ACTION_SEND_DATA);
sendIntent.putExtra(BluetoothService.EXTRA_BLUETOOTH_DATA, myString.getBytes());
//Sends the intent to any BroadcastReceivers that have registered receivers for its action.
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
Unfortunately I have class in a few minutes and can't finish this post right now, but I'll be on in a few hours to cover how to set up the receiving part. In the meantime, feel free to check out this code from a project of mine that solves exactly these problems. Look at the TransferManager class and how it uses Threads to provide a non-blocking way to receive data from the InputStream of the BluetoothSocket.
==========================================================================
Ok, now let's look at how you can use your Service to receive data from your remote Bluetooth device. One thing to know about Services is that they are not run on separate threads from your Activities. While they maintain their state and their lifecycle functions are decoupled from those of Activities, they are still both executed on the main UI thread. This means that if you put code in your Service that is slow or blocking, it will respectively slow down or freeze your Activity's UI. This is behavior that we definitely want to avoid, so when we consider receiving data from a Bluetooth device (a blocking operation), we need to handle that operation by creating a new Thread within the custom Service class. Let's define a custom class that extends Thread as an inner class of our BluetoothService:
public class BluetoothService extends Service {
...
public void onCreate() {...}
public int onStartCommand(...) {...}
public static class ReceiveThread extends Thread {
private boolean isRunning;
private InputStream mBluetoothInputStream;
public ReceiveThread(InputStream bluetoothInputStream) {
mBluetoothInputStream = bluetoothInputStream;
isRunning = true;
}
#Override
public void run() {
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(mBluetoothInputStream));
String line;
while(isRunning) {
try {
//This is the line that blocks until a newline is read in.
line = bufferedReader.readLine();
} catch(IOException ioe) {
//This happens if the InputStream is closed.
ioe.printStackTrace();
//Stop the thread from looping.
isRunning = false;
}
//Make sure our line in isn't null or blank.
if(line == null || line.equals("") {
continue; //Start again at top of while loop.
}
//Notify your Activity about the new data.
Intent receivedIntent = new Intent(BluetoothService.this, MyActivity.class);
receivedIntent.setAction(ACTION_RECEIVED_DATA);
receivedIntent.putExtra(EXTRA_BLUETOOTH_DATA);
LocalBroadcastManager.getInstance(BluetoothService.this).sendBroadcast(receivedIntent);
try {
//This is an arbitrary sleep time just to prevent
//this from looping without any restriction.
Thread.sleep(20);
} catch(InterruptedException e) {
//This happens if the Thread is interrupted for any reason.
e.printStackTrace();
isRunning = false;
}
}
}
}
}
Ok, now you can spin up a new ReceiveThread by throwing a few lines onto the end of onStartCommand() in the Service:
ReceiveThread receiver = new ReceiveThread(mSocket.getInputStream());
receiver.start();
The last step is to actually get that data into your Activity. To do that, you'll create a BroadcastReceiver that listens for the broadcasts sent out by the ReceiveThread. In your Activity class, put this at the end of onCreate():
public void onCreate() {
...
LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//Get your data out of the intent.
byte[] data = intent.getByteArrayExtra(BluetoothService.EXTRA_BLUETOOTH_DATA);
}
}, new IntentFilter(BluetoothService.ACTION_RECEIVED_DATA));
}
The onReceive() method gets called every time your BluetoothService's ReceiveThread reads a new line from your remote bluetooth device. Depending on your actual application, this may or may not be suitable for you (for example, if your program isn't text/command based and has no newline characters in it). You can change that behavior by swapping out the BufferedReader in the ReceiveThread with another type of Reader.
EDIT:
In your snippet you've built a stub method called write that you seem to be fixated on having. Having a method like this would require you to execute it as a direct call from the Activity, which isn't what you want. If you look up in this post, you'll see that I've put some code that was meant to be called from your Activity which uses intents to deliver your data to the Service to be written. Look at the snippet beginning with public class MyActivity extends Activity. The point of using intents is that the Android framework will take care of carrying the "extra" data over to the Service, which is then unpackaged in the onReceive() method in onStartCommand() in the Service, where you can see the OutputStream is being written to.
The only other thing is that I did forget the return Service.START_STICKY for the onStartCommand() method of the Service. Everywhere you would want to put that write method that you made in your snippet, put the code about creating and sending the Intent using the LocalBroadcastManager.
I'm a novice at Java and Android programing, although I have done some extensive PHP coding, so I understand most concepts fairly quickly. What I don't know is "the way" to do things.
As a practice app, I want to code a Fast Fourier Transform app that shows me the recorded audio spectrum (I'm a physics major, so this seems like a worthwhile and fun little project.) (Yes, I know it's been done before).
So far I've set up the AudioRecord reader and I got it to read bytes, I figured out I need to create a Thread which posts to a Handler if I want to keep it running in the background without freezing the app, and I figured out how to pass data to the Handler. For now it's just displaying the bytes in a TextView in the MainActivity, so I can "see" something is happening.
Where I'm stuck is that apparently because my Handler isn't static, GC trashes something (I don't know what) after a few seconds of my app running flawlessly, and it crashes. After some reading, I seem to have figured out I need to extend Handler and implement some WeakReference, but, in all honesty, it's getting to the point where I don't know what I'm doing. If anyone could explain what exactly is going on here, or how I can reference my TextView from some external class, I'd be very grateful. Thanks in advance, EastWind
Here's the code:
private Handler uiCallback = new Handler () {
public void handleMessage (Message msg) {
tv.setText(Arrays.toString(msg.getData().getByteArray("data")));
}
};
#Override
protected void onResume() {
super.onResume();
tv = (TextView) findViewById(R.id.mainView);
bufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
ar = new AudioRecord(AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
shortbuffer = new short[bufferSize];
bytebuffer = new byte[bufferSize];
ar.startRecording();
tv.setText("Hello");
int N = 0;
// now you can start reading the bytes from the AudioRecord
Thread t = new Thread() {
public void run() {
Bundle b = new Bundle();
Message msg = Message.obtain();
while (true) {
ar.read(bytebuffer, 0, bytebuffer.length);
byte[] pkt = Arrays.copyOf(bytebuffer, bytebuffer.length);
//tv.setText(Arrays.toString(pkt));
b.putByteArray("data", pkt);
msg.setData(b);
uiCallback.sendMessage(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
t.start();
}
#Override
protected void onPause() {
super.onPause();
ar.stop();
}
you are sending the same Message multiple times in a loop, you have to obtain a new Message before sending it
The weak reference is a tip for avoiding access to views that have been removed from their container (the activity for example).
A possible alternative solution is that you do something like this:
class YourSpecialAudioClass implements ISpecialAudioClass
{
/**this class can be used by the outside UI world, inside the implementation of the runnable in the CTOR*/
#override
public byte[] getData(){...}
public YourSpecialAudioClass(Runnable doSomethingWithDataRunnable)
{
this.doSomethingWithDataRunnable=doSomethingWithDataRunnable;
this.handler=new Handler();
}
...
while(true)
{
...
this.handler.post(this.doSomethingWithDataRunnable);
}
...
}
don't forget to handle stopping the thread from updating the UI when you don't need it, especially if the activity is being closed.
I would like to know when into an Activity Android execute a function itself and when is necessary to call a function. For example in the following script I downloaded, the first 4 methods are executed without calling it, but the last one sendMessage(), needs to be called:
public class BroadcastChat extends Activity {
// Debugging
private static final String TAG = "BcastChat";
private static final boolean D = true;
// Message types sent from the BluetoothChatService Handler
public static final int MESSAGE_READ = 1;
public static final int MESSAGE_WRITE = 2;
public static final int MESSAGE_TOAST = 3;
// Key names received from the BroadcastChatService Handler
public static final String TOAST = "toast";
// Layout Views
private ListView mConversationView;
private EditText mOutEditText;
private Button mSendButton;
// Array adapter for the conversation thread
private ArrayAdapter<String> mConversationArrayAdapter;
// String buffer for outgoing messages
private StringBuffer mOutStringBuffer;
// Member object for the chat services
private BroadcastChatService mChatService = null;
// The Handler that gets information back from the BluetoothChatService
private final Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
if(D) Log.e(TAG, "[handleMessage !!!!!!!!!!!! ]");
switch (msg.what) {
case MESSAGE_WRITE:
byte[] writeBuf = (byte[]) msg.obj;
// construct a string from the buffer
String writeMessage = new String(writeBuf);
mConversationArrayAdapter.add("Me: " + writeMessage);
break;
case MESSAGE_READ:
String readBuf = (String) msg.obj;
mConversationArrayAdapter.add("You: " + readBuf);
break;
case MESSAGE_TOAST:
Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
Toast.LENGTH_SHORT).show();
break;
}
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(D) Log.e(TAG, "+++ ON CREATE +++");
// Set up the window layout
setContentView(R.layout.main);
}
public void onStart() {
super.onStart();
if(D) Log.e(TAG, "++ ON START ++");
setupChat();
}
#Override
public synchronized void onResume() {
super.onResume();
if(D) Log.e(TAG, "+ ON RESUME +");
mChatService.start();
}
private void sendMessage(String message) {
if(D) Log.e(TAG, "[sendMessage]");
// Check that there's actually something to send
if (message.length() > 0 ) {
// Get the message bytes and tell the BluetoothChatService to write
byte[] send = message.getBytes();
mChatService.write(send);
// Reset out string buffer to zero and clear the edit text field
mOutStringBuffer.setLength(0);
mOutEditText.setText(mOutStringBuffer);
}
}
... Incomplete script, just a part shown for the question
}
So my question is double:
1- In an Android Activity are the methods called sequencially from the first line to the last one?, Is there a loop that makes the "pointer" go back to the first line once the last one is reached?
2-How can you determine which methods are going to be executed automatically (like onCreate() ) and which are going to wait until they are called by another method of the script.
Thank you very much for your time
The first thing to understand, and it's a vital point, is that this is not a script, it's code. If you think of it as a script, you won't "get" what the code is doing. A script executes from start to finish. It might branch out into a function but ultimately, things happen in order.
In Java ( and therefore Android), everything happens as a response to an event or a callback. Some of these events are raised by Android and Nickolaus has already pointed you to the Activity lifecycle which documents callbacks made by Android to your Activity and the precise order in which they happen. Other events are raised by Receivers, ContentProviders, Listeners etc.
Note that this order is not time based (although of course you can create time based code events) and doesn't happen one after another. They are called when the state of the Activity changes, and only when the state changes.
In the handlers for these callbacks, you can of course call your own functions, create instances of classes and call their methods, and do stuff in order, from top to bottom - but only inside the handler.
The first thing that happens when your app starts is that Android instantiates the Application class. Every app has an instance of the Application class, whether you know it or not, and that Application class instance also has a lifecycle similar to an Activity, so Application.onCreate() is the first event in the application to be fired. Once the Application class instance is instantiated, then the main activity, defined in you rmanifest, is created and it's onCreate() method is called.
After that, everything happens in response to a callback from, for example listeners (onClick, onReceive etc) or in response to events. From the end of your onCreate(), your code only executes when some other event happens.
You can shorten all this, and answer your question,by saying that sendMessage can only be called from somewhere inside a callback handler.
It gets more complicated when there are multiple threads executing code but that's for another day.
I hope that this helps rather than makes things more confusing!
Here take a look at the activity lifecycle this should explain it to you.
ive been thinking about this for hours and im not closer to an solution!
My thread just stops looping when im fetching a message from an server for some reason, and works perfectly when im not doing it.
This works and prints refreshing every second:
public class ChatRoom extends Activity implements OnClickListener, Runnable {
private Thread t = new Thread(this);
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chatroom);
Button send = (Button) findViewById(R.id.send);
send.setOnClickListener(this);
Intent receiver = getIntent();
String host = receiver.getStringExtra("Host");
int port = receiver.getIntExtra("Port", 4456);
try
{
socket = new Socket(host, port);
this.receive = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
this.send = new PrintWriter(this.socket.getOutputStream(), true);
}
catch(IOException ioe) { System.out.println(ioe); }
t.start();
}
public void run()
{
String message = "";
while(true)
{
try
{
// message = receive.readLine(); BufferedReader
t.sleep(1000);
}
//catch(IOException ioe) { System.out.println(ioe); }
catch (NullPointerException npe) { System.out.println(npe); }
catch (InterruptedException e) { System.out.println(e); }
System.out.println("Refreshing...");
}
}
And when i use my commented code, it actually works and i get a message from the server but it loops just once! Why is that?
Output:
Server Message
Refreshing...
I get no Exception or errors, but i had an error before with some similar code that said that i cant change UI on other threads. So ive been looking at some runOnUiThread but it didnt make it better, and i dont know why it should :(
The method BufferedReader.readLine() blocks until a newline character is received. If there is no newline in your receiver stream it will block forever.
A few things here:
Swap from System.out.println("string"); to Log.d("tagname","string"); then look on DDMS for output lines.
I don't think you're creating a thread properly, and you certainly aren't providing any interface to kill it, which may cause issues when you test it. I would separate the thread into a new file, say NameOfThread:
//File "NameOfThread"
public class NameOfThread extends Thread{
//any fields you want here to mess with e.g.
private String message;
private boolean running;
public NameOfThread(){
message = "";
running = true;
}
#Override
public void run(){
while(running){
//do stuff
}
}
public void setRunning(boolean run){
running = run;
}
}
//When you want to call it
NameOfThread varThread = new NameOfThread();
varThread.start();
//when you want to kill the thread
varThread.setRunning(false);
You may think 'why bother with this whole running variable junk, I don't need it.' but how else will this thread end gracefully? There is another method of killing the thread properly, which is using InterruptedException and your cleanup code goes there, but that's just an alternative.
Try doing this first, then you'll need to sort out the message itself (the method you're using currently isn't great since readLine() will block until a line is received (meaning you'll get "Refreshing..." when you get a new line rather than once per second.
You're surely getting some exceptions thrown, you just can't see them cause you're trying to print them on the standard output, which is missing on Android. Your exception is handled correctly and the code finishes. To properly get the exception information use Logs, or just throw a RuntimeException. Hope this helps.