I've searched for the relationship between thread and looper, and I just know what the two words's literal meaning now. When come to specific conditions, I'm still a little confused.
I came across this issue when I try to build an app about communicating with a bluetooth device. I got problems in the connect thread.
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private String mSocketType;
public ConnectThread(BluetoothDevice device, boolean secure) {
mmDevice = device;
BluetoothSocket tmp = null;
mSocketType = secure ? "Secure" : "Insecure";
// Get a BluetoothSocket for a connection with the
// given BluetoothDevice
try {
if (secure) {
tmp = device.createRfcommSocketToServiceRecord(
MY_UUID_SECURE);
} else {
tmp = device.createInsecureRfcommSocketToServiceRecord(
MY_UUID_INSECURE);
}
} catch (IOException e) {
tip("Socket Type: " + mSocketType + "create() failed");
}
mmSocket = tmp;
}
public void run() {
Looper.prepare();
setName("ConnectThread" + mSocketType);
// Always cancel discovery because it will slow down a connection
if(mBluetoothAdapter.isDiscovering())
mBluetoothAdapter.cancelDiscovery();
// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a
// successful connection or an exception
mmSocket.connect();
} catch (IOException e) {
// Close the socket
try {
mmSocket.close();
} catch (IOException e2) {
e2.printStackTrace();
}
connectionFailed();
return;
}
// Reset the ConnectThread because we're done
synchronized (BluetoothThreads.this) {
mConnectThread = null;
}
// Start the connected thread
connected(mmSocket, mmDevice, mSocketType);
Looper.loop();
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
}
}
}
this code is download from the Android Developers's sample. and the Looper.prepare(), Looper.loop() are added by myself. Withou calling these two methods, the app will crash down. And I got a warning from the android studio:Can't create handler inside thread that has not called Looper.prepare(). That's why I add the two methods.
I want to ask, do I call the two methods in the right way?
Why I must call them while I have not used Toast or Handler as others do?
I want to ask, do I call the two methods in the right way?
Yes
Why I must call them while I have not used Toast or Handler as others do?
Looper.loop() and Looper().prepare() are used to create a MessageQueue and to handle this MessageQueue android recommends to use Handler , so if you use Looper.loop() and Looper.prepare() then you should use Handler also .
Well, finally I figure out what's wrong with my own project. There is nothing to do with the build.gradle, but thank Android Dev all the same.
Actually it's myself to blame. In the sample project BluetoothChat, methods like Log.d() is called to indicate the debug information. But I didn't know where to find Log.d()'s result, so I replaced them with Toast. I thought that would be fine. But I find out that Toast cannot be used in ConnectThread, otherwise it will throw an Exception:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
And I used Toast in method connectionFailed(), that's where the problem is.
Actually, you do not need Looper.prepare() and Looper.loop(). Those are only required when you would like to attach a Handler to the thread, which in your case, I cannot see such requirement.
Essentially, Once you call Looper.prepare(), It assigns a message queue to this thread such that all subsequent messages passed by handlers will be handled one by one in a queue manner.
Note: For debugging always use Log.d() / Log.w() / Log.e() and avoid using toasts.
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)
It's there any way to set connection timeout to BluetoothSocket?
If my device is offline, connection process takes a few seconds and then returns an error. I need to set the timeout to 1 second. It is possible?
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(APP_UUID);
// socket.setProxyConnectionTimeout(1000); <- some like this
socket.connect();
BluetoothSocket class have PROXY_CONNECTION_TIMEOUT, but never used...
Thanks for answers.
BTW:
I try some like this:
socket.connect();
Thread.sleep(1000);
socket.close(); // but socket is not closed & still connecting
You can't change timeout of BluetoothSocket.connect(). As documentation:
This method will block until a connection is made or the connection fails. If this method returns without an exception then this socket is now connected.
A workaround.
Ex: timeout 5s. Using CountDownTimer to check if connect is complete(success or fail). After 5s, if connection is incomplete then use BluetoothSocket.close() to cancel.
As BluetoothSocket documentation:
close() can be used to abort this call from another thread.
Ok this code is only slightly tested but it works so far:
Task.Run(() =>
{
while (true)
{
socket = pairedBTDevice.CreateRfcommSocketToServiceRecord(UUID.FromString(uuid));
socket.ConnectAsync();
Thread.Sleep(1000);
if (socket.IsConnected)
{
// call a function here
// my function blocks for the application lifetime
return;
}
else
{
socket.Close();
}
}
});
I hope this helps.
socket.connect();
This method will block until a connection is made or the connection
so.if connect could not establish.
Thread.sleep(1000);
Would not excute.
Or other method behind socket.connect(); would not execte too.
In the android documentation, the following code occurs in the run() segment of a thread:
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
However, the accept() method blocks the thread. I therefore do not understand why a while() loop is needed, especially since in all possible situations the while loop is broken in its first run.
Any ideas?
Normally there wouldn't be a break after accepting and processing one socket: you would loop accepting sockets indefinitely.
It's a stupid example.
Ok, I am new to android, I'm trying to create an app that interfaces with an arduino via bluetooth. I've seen the sample BluetoothChat and seen how it's using an Handler to communicate between the "service", the threads spawned by it and the MainActivity.
My problem is that I have more than one Activity that needs to use the Bluetooth Service.
For each Activity I have an Handler like this:
mHandler = new Handler(){
#Override
public void handleMessage(Message message) {
switch (message.what){
case BtService.CHANGE_STATE:
if (message.arg1 == BtService.STATE_CONNECTING){
Intent i = new Intent (MainActivity.this,ConnectedActivity.class);
startActivity(i);
}
break;
}
}
};
and in the service constructor I've got this:
private BtService(){
btm = BluetoothAdapter.getDefaultAdapter();
mHandler= new Handler(Looper.getMainLooper());
}
and when I need to send a message I do this:
private synchronized void setState(int state){
mHandler.obtainMessage(CHANGE_STATE, state, -1).sendToTarget();
mState = state;
}
but the messages aren't received in the various other Handlers.
In here is stated that "all of the Handler objects for a particular thread receive the same message." so I can't understand the problem.
Do I need, every time an activity is started, to pass to the service the Handler declared in that Activity to have it receive messages? This seems to work, but it dosen't seem to be a good practice for me.
If you want send the message in all application you should use BroadcastReceiver, I this this is the best way in your case.
Intent intent = new Intent(ApplicationConstants.MY_MESSAGE);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
Receive message in any activity(you cand use this in more then one activity)
BroadcastReceiver connectionUpdates = new BroadcastReceiver() {
#Override
public void onReceive(Context arg0, Intent intent) {
...//TODO here
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(
connectionUpdates ,
new IntentFilter(ApplicationConstants.MY_MESSAGE));
Hope this is helpfull
Cheers,
Instead of having each activity connect through bluetooth, you can extend the Application layer and use that to maintain thread(s) to retrieve and manage the data collected over the bluetooth connection. Then just use a handler in each activity to have them refresh against the data gathered in the Application layer, if needed.
My only Activity with the btAdapter and socket is the first activity to actually need bluetooth information (after menus and bt config activities).
In my first activity onRusume() looks something like this with comments explaining..:
#Override
public void onResume() {
super.onResume();
Log.d(TAG, "...onResume - try connect...");
// Set up a pointer to the remote node using it's address.
BluetoothDevice device = btAdapter.getRemoteDevice(address);
// Two things are needed to make a connection:
// A MAC address, which we got above.
// A Service ID or UUID. In this case we are using the
// UUID for SPP.
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
errorExit("Fatal Error", "In onResume() and socket create failed: " + e.getMessage() + ".");
}
// Discovery is resource intensive. Make sure it isn't going on
// when you attempt to connect and pass your message.
btAdapter.cancelDiscovery();
// Establish the connection. This will block until it connects.
Log.d(TAG, "...Connecting...");
try {
btSocket.connect();
Log.d(TAG, "....Connection ok...");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
errorExit("Fatal Error", "In onResume() and unable to close socket during connection failure" + e2.getMessage() + ".");
}
}
// Create a data stream so we can talk to server.
Log.d(TAG, "...Create Socket...");
/**
* **Here I am kicking off the thread in the application that retrieves all data
* needed by all my activities. Then it stores the information in its member
* variables. Each activity then refreshes as often as needed, gets the data from
* the application layer it needs and does some logic on it.**
*/
if(mConnectedThread == null) {
mConnectedThread = app.new ConnectedThread(btSocket);
mConnectedThread.start();
}
// This kicks off the handler for this activity that refreshes the activity every
// xxxx ms and checks the data retrieved from bt in the application layer.
startUpdatingTicketView();
}
That is pretty much the core of how i got it to work for me.
Just an additional note... I also tried doing this with the bt communication managed in a background service and could not get it working well. I forget exactly what the issues I was running into were and it is quite possible using a service would work as well, but I did not end up going this route.
Good luck.
I modified the standard Bluetoothchat example to send 4 bytes of data at a time to a bluetooth device every half a second. It works fine if I start the App fresh. However, there is a problem if I reconnect as follows:
While Bluetooth is connected, I click the connect button again on the menu and select the same device. This disconnects the bluetooth (not sure whether this is the right procedure to disconnect). Then, I connect again by selecting the device, and it will be reconnected. After reconnection, a very strange problem appears: instead of sending the data every half a second, it will send the data every quarter a second. If I go through the process again and reconnect, the time interval will become even shorter. It gets to a point that the bluetooth device on the receiving end can't keep up with the data. At this point, the only way out is to kill the app and restart again. Then everything becomes normal, till next time I try to reconnect again.
I have tried different things but nothing appear to fix this. For example, I made sure the thread sending the data is killed when disconnected so no multiple threads are sending the data. I was wondering whether the baud rate changed when reconnected, but then why would the baud rate affect the Thread.sleep(500); statement (which is responsible for controlling the half a second data send). Any help would be greatly appreciated.
Here is the code, the SendClass is created under the MainActivity:
class SendClass implements Runnable {
public void run() {
bytearr[0]=0;bytearr[1]=0;bytearr[2]=0;bytearr[3]=0;
while (!Thread.currentThread().isInterrupted()) {
if (mChatService==null || mChatService.getState()
!=BluetoothChatService.STATE_CONNECTED) {
continue;
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mChatService.write(bytearr);
}
}//end of run
}//end of runnable
Then under STATE_CONNECTED:
case BluetoothChatService.STATE_CONNECTED:
setStatus(getString(R.string.title_connected_to,mConnectedDeviceName));
/*
if(sendingThread!=null){
//sendingThread.stop();
sendingThread.interrupt();
if(D) Log.i(TAG, "after sendingThread");
sendingThread = null;
}*/
sendingThread = new Thread(new SendClass());
sendingThread.start();
break;
As you can see, I tried to kill the thread before creating a new one but that didn't make any difference. Any suggestions?
You are creating a thread that never actually stops, even after you create a new thread and assign to the same variable that particular thread wont stop running.
You need to make sure that the thread will stop after it disconnects.
Here is my suggestion
Change your SendClass to:
class SendClass implements Runnable {
private boolean stopped = false;
public void setStopped(boolean s){
this.stopped = s;
}
public void run() {
bytearr[0]=0;bytearr[1]=0;bytearr[2]=0;bytearr[3]=0;
while (!Thread.currentThread().isInterrupted() && !stopped) {
if (mChatService==null || mChatService.getState() !=BluetoothChatService.STATE_CONNECTED) {
continue;
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mChatService.write(bytearr);
}
}//end of run
}//end of runnable
Then when you start your thread keep the reference to the Runnable so you can call the setStopped(true); like this
SendClass sc = new SendClass();
sendingThread = new Thread(sc);
sendingThread.start();
When you disconnect the bluetooth dont forget to call sc.setStopped(true); so your thread will finish by not going into the while.