Using a static Handler class, referencing external TextView - android

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.

Related

Bluetooth thread / Handler doesn't update the UI

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.

Reading data from USB host every 200ms in android app

My USB Host is receiving sensor data and it is getting updated every 200ms. I would like to read this data in my android app every 200ms. I am able to read it using bufferreader, It reads the data for sometime and then hangs. It is not consistent. I am new to this and may be I am not doing it the correct way. Below please find my code and let me know your suggestions. Thanks in advance.
public void startProcessOne()
{
new CountDownTimer(110,100)
{
#Override
public void onTick(long millisUntilFinished)
{
StringBuilder text = new StringBuilder();
line = "";
try {
FileReader in = new FileReader("/mnt/udisk/TEST.TXT");
BufferedReader br = new BufferedReader(in);
int i=0;
char[] buf = new char[10000];
while((i = br.read(buf,i,100))!= -1)
{
String h = new String(buf);
text.append(h);
text.append('\n');
}
br.close();
}
catch (IOException e) {
//You'll need to add proper error handling here
}
TxtRead.setText(text.toString());
}
#Override
public void onFinish()
{
startProcessOne();
}
}.start();
}
TxtRead.setText(text.toString());
This line is causing the problem. You can't touch UI elements from a background thread. You should instead run those codes in the UI/Main thread.
In your case, I'd personally prefer using Java threads. So, create a background thread to keep running periodically. If you would need to run UI methods from that background thread. You probably need a handler attached to the main thread.
// Instantiate a handler in UI thread
final Handler handler = new Handler();
new Thread(new Runnable(){
// Once you're done and want to break the loop, just set this boolean
private boolean stopped = false;
#Override
public void run(){
while(!stopped) {
// Read from the file
// Whenever you need to update an UI element,
// you should wrap it inside this runnable object
handler.post(new Runnable(){
#Override
public void run(){
// Update UI
TxtRead.setText("new_text");
}
})
try {
// This thread will sleep for 9 seconds
Thread.Sleep(9000);
} catch(Exception e){}
}
}
}).start();

Android Thread vs AsyncTask vs IntentService called from BLE onCharacteristicChanged()

I have an Android app from which I receive BLE data (every 62ms via notifications). The app can save data via a BufferedWriter to a file. Upon each onCharacteristicChanged() callback, I call either an AsyncTask, Thread or an IntentService to do a file write if the user enabled file save.
The AsyncTask seems to work fine. But the docs say execute must be invoked on the UI thread, and I'm calling it from the BLE callback. Is that a problem? And how should I fix it?
Using Thread causes this error: GKI_exception out of buffers https://code.google.com/p/android/issues/detail?id=65455 (except my code is not scanning but receiving notifications) and if the file save is long, I need to power cycle the Nexus 7 (the app and BLE become totally unresponsive). Why does the Thread not work and how can I fix it?
The IntentService never goes to the onHandleIntent(). What are the issues here?
Here is some code:
...
_context = this.getApplicationContext();
...
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
...
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
...
int mode = 1;
if (mode==0) // Asynctask
new doFileWriteTask().execute(strBuild.toString());
else if (mode==1) // Thread
{
final String str = strBuild.toString();
new Thread(new Runnable() {
public void run() {
try {
_writer.write(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
else if (mode==2) // intentService
{
Intent mServiceIntent = new Intent(_context, writeFileService.class);
mServiceIntent.putExtra("foo", strBuild.toString());
startService(mServiceIntent);
}
}
...
};
private class doFileWriteTask extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... strings) {
try {
_writer.write(strings[0]);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private class writeFileService extends IntentService {
public writeFileService() {
super("writeFileService");
}
#Override
protected void onHandleIntent(Intent workIntent) {
String dataString = workIntent.getStringExtra("foo");
try {
_writer.write(dataString);
} catch (Exception e) {
e.printStackTrace();
}
}
}
...
But the docs say execute must be invoked on the UI thread, and I'm calling it from the BLE callback. Is that a problem? And how should I fix it?
The framework triggers the AsyncTask callback methods on the same thread it was called from (presumed to be the main thread). It doesn't really affect the background work, but you could see problems if you started trying to use onPostExecute() and the like. AsyncTask probably isn't the best choice to be called from a thread that you don't have control over.
Why does the Thread not work and how can I fix it?
I can't say exactly why you are still seeing errors, through spawning a series of private unsynchronized threads will probably lead to other headaches. If you want to use a single worker thread, a better choice would be to use a single HandlerThread that you can post to from your event callbacks using a Handler, something like:
…
_workerThread = new HandlerThread("Worker");
_workerThread.start();
_handler = new Handler(_workerThread.getLooper(), new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
String str = (String) msg.obj;
_writer.write(str);
return true;
}
});
…
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
…
Message msg = Message.obtain(_handler, 0, strBuild.toString());
_handler.sendMessage(msg);
…
}
That solution is quite a bit more code, but given the frequency of writes this is probably the most efficient choice.
The IntentService never goes to the onHandleIntent(). What are the issues here?
You should pretty much never implement a top level Android component (activity, service, content provider, receiver) as an inner class, because they have to be declared in your manifest as well (and the XML syntax for inner classes is ugly). If your service does not have a matching entry in the manifest, then you will never see it start. You might want to have a look at the docs on using services.
At a minimum, a Service written as an inner class must be public static to work. Otherwise the framework cannot see it and cannot instantiate it using a default constructor (non-static inner classes mess with the constructor). Unless you are calling startService() inside of a try/catch right now, I'm surprised it isn't crashing when you attempt this.
IntentService is probably the simplest of your three choices because it is the most decoupled and the framework will handle queueing up work and tearing down the threads when all the incoming work is done.

Variable not updated within handler.post's run method in Android

I created a new thread to handle my TCP management needs. I did this in order to keep the UI thread active while it's doing all sorts of socket magic. The problem I'm having is that the variable num used in ServerThread doesn't seem to updating within the handler.post()'s run method. It changes once but subsequent iterations within my while(true) loop no longer changes its value. However, outside handler.post() I noticed that it is changing properly. I have included a log command for logcat in order to see the values of num so that's why I know this is what is happening. The thing is, I need the variable num inside the run() method to update a few things in the UI thread.
May be there's something wrong with the way I declared num. I did not include in the code most of the things I think is irrelevant to my question, so if I missed something please let me now. This is my first adventure into android java threading so some help would be really appreciated. Just run out of mental energy tonight.
public class MainActivity extends Activity {
private int num = 0;
private Handler handler = new Handler();
//Other declarations
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Do bunch of things including starting ServerThread
//and creating a TCP server socket
}
public class ServerThread implements Runnable{
public void run(){
// ...
// Do bunch of stuff including waiting for a client to connect
try{
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
while(true){
num = in.read();
Log.e("MainActivity", "A:" +num); //<-- WORKS FINE
if(num == -1) //client socket closed
break;
handler.post(new Runnable() {
#Override
public void run() {
Log.e("MainActivity", "B:" +num); //<-- DOES NOT WORK FINE
//Do bunch of stuff here with the variable num
//including updating the UI thread
}
}
}catch(Exception e){
// ...
}
}
}

Thread only loops once

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.

Categories

Resources