Background: I have a IOIO which I am using to measure the output from an photodiode, this is then converted into a digital output. I need to find the frequency at which the signal changes between 1 and 0. Someone gave me some code that I could use to measure the frequency, but I have no idea how to integrate it into my existing app. I know that the way I have implemented it will not work because the UI thread which updates the variable is waiting on the return from the other thread that calculates the frequency, so that thread will only get the value of diode when it starts running. So how do I make the frequency thread have a real-time value for diode and also after it has calculated the frequency pass it back to the UI thread to be displayed?
Here is my UI thread (FrequencyApp.java):
public class FrequencyApp extends IOIOActivity {
private TextView textView_;
private TextView textView2_;
private TextView textView3_;
private ToggleButton toggleButton_;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textView_ = (TextView)findViewById(R.id.TextView);
textView2_ = (TextView)findViewById(R.id.TextView2);
textView3_ = (TextView)findViewById(R.id.FrequencyLabel);
toggleButton_ = (ToggleButton)findViewById(R.id.ToggleButton);
enableUi(false);
}
class Looper extends BaseIOIOLooper {
private AnalogInput input_;
private DigitalOutput led_;
volatile int diode;
private long frequency;
#Override
public void setup() throws ConnectionLostException {
try {
input_ = ioio_.openAnalogInput(31);
led_ = ioio_.openDigitalOutput(IOIO.LED_PIN, true);
enableUi(true);
} catch (ConnectionLostException e) {
enableUi(false);
throw e;
}
}
#Override
public void loop() throws ConnectionLostException {
try {
led_.write(!toggleButton_.isChecked());
float reading = input_.getVoltage();
if(reading > 1){
diode = 1;
} else {
diode = 0;
}
if(toggleButton_.isChecked()){
FrequencyThread frequencyTaskThread = new FrequencyThread();
frequencyTaskThread.setPriority(Thread.NORM_PRIORITY-1); //Make the background thread low priority. This way it will not affect the UI performance
frequencyTaskThread.start();
frequency = (long) frequencyTaskThread.run(diode);
frequencyTaskThread.stop();
}
setText(Float.toString(reading), Long.toString(diode), Long.toString(frequency));
Thread.sleep(100);
} catch (InterruptedException e) {
ioio_.disconnect();
} catch (ConnectionLostException e) {
enableUi(false);
throw e;
}
}
}
#Override
protected IOIOLooper createIOIOLooper() {
return new Looper();
}
private void enableUi(final boolean enable) {
runOnUiThread(new Runnable() {
#Override
public void run() {
toggleButton_.setEnabled(enable);
}
});
}
private void setText(final String str,final String str2,final String str3 ) {
runOnUiThread(new Runnable() {
#Override
public void run() {
textView_.setText(str);
textView2_.setText(str2);
textView3_.setText(str3);
}
});
}
}
Here is the thread for calculating the frequency(FrequencyThread.java:
public class FrequencyThread extends Thread {
public float run(int diode){
// Find frequency to the nearest hz (+/- 10%)
// It's assumed that some other process is responsible for updating the "diode"
// variable. "diode" must be declared volatile.
long duration = 1000; // 1 second
final int interval = 100; // sampling interval = .01 second
int oldState = diode;
int count = 0;
final long startTime = System.currentTimeMillis();
final long endtime = startTime + duration;
while (System.currentTimeMillis() < endtime) {
// count all transitions, both leading and trailing
if (diode != oldState) {
++count;
oldState = diode;
}
Thread.sleep(interval);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// find the actual duration
duration = System.currentTimeMillis() - startTime;
// Compute frequency. The 0.5 term is because we were counting both leading and
// trailing edges.
float frequency = (float) (0.5 * count / (duration/1000));
return frequency;
}
}
The way you have it set up easiest would probably be to use a Handler to send messages from the FrequencyThread to the main thread. The way that is probably preferred to use an AsyncTask to abstract away the Thread / Handler and make the whole situation a bit easier to deal with. Doing so might require you to put the IOIOLooper thing into an AsyncTask though, I have no experience with that board or its Java API.
Also you shouldn't need the "runOnUiThread", or the Runnable at all in your setText() method. That seems to be only being called from the main thread anyway.
What you'll want to do is override your Handlers handleMessage() to call setText(). Then inside the FrequencyThread make a call to handler.sendMessage() passing back the data and / or message(i.e. error).
I tried using the code you've posted to make an example but I am having trouble following it honestly.
Related
My app recently reached the "maximum" size of 100 MBs so I had to learn about the apk expansion procedure. I read google's documentation but i found it kind of bad so i looked around and followed this guide: https://kitefaster.com/2017/02/15/expansion-apk-files-android-studio/ . Now, after adding the SampleDownloaderActivity,SampleDownloaderService and the SampleAlarmReceiver classes i got lost. I created a directory in my phone's internal storage in this location Android/obb/com.mypackage.example like the Google documentation mentions so i can put my expansion files there. But here are my questions:
1) I've added the required permissions in the app's manifest.xml file but do i need to ask for them first through code in order for the receiver,downloader and the downloaderActivity to work?
2) The files that i want to include in my main expansion file are some images from my xhdpi drawable folder but i am not sure on what i need to do in order to create my .obb file containing those images. Do i need to create a .zip file with them? Do i just drop them inside the directory i created that i mentioned above? What do i need to do?
3) Supposing the previous questions have been answered, if the files already exist in the directory or have been downloaded, i must get that dir through code and read the files in order to use them in my app, correct?
4) If the files are not there,how do i initiate the download from Google-Play? From what i understand by the doc, i need to make my app's main activity the "SampleDownloaderActivity" one, right?
5) After the files are done downloading, do i have to create an intent in the SampleDownloaderActivity's onCreate method in order for the app to go to my desired activity which uses those files?
Below, i'm posting the apk expansion related code files in which i've changed what i understood was needed. Do i need to change something else? Thank you in advance for your help!
ApkExpDownloaderService.java
public class ApkExpDownloaderService extends DownloaderService {
// stuff for LVL -- MODIFY FOR YOUR APPLICATION!
private static final String BASE64_PUBLIC_KEY = "MY_KEY";
// used by the preference obfuscater
private static final byte[] SALT = new byte[] {
// my array of bytes
};
/**
* This public key comes from your Android Market publisher account, and it
* used by the LVL to validate responses from Market on your behalf.
*/
#Override
public String getPublicKey() {
return BASE64_PUBLIC_KEY;
}
/**
* This is used by the preference obfuscater to make sure that your
* obfuscated preferences are different than the ones used by other
* applications.
*/
#Override
public byte[] getSALT() {
return SALT;
}
/**
* Fill this in with the class name for your alarm receiver. We do this
* because receivers must be unique across all of Android (it's a good idea
* to make sure that your receiver is in your unique package)
*/
#Override
public String getAlarmReceiverClassName() {
return ApkExpAlarmReceiver.class.getName();
}
}
ApkExpDownloaderActivity.java
public class ApkExpDownloaderActivity extends Activity implements IDownloaderClient {
private static final String LOG_TAG = "LVLDownloader";
private ProgressBar mPB;
private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;
private View mDashboard;
private View mCellMessage;
private Button mPauseButton;
private Button mWiFiSettingsButton;
private boolean mStatePaused;
private int mState;
private IDownloaderService mRemoteService;
private IStub mDownloaderClientStub;
private void setState(int newState) {
if (mState != newState) {
mState = newState;
mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
}
}
private void setButtonPausedState(boolean paused) {
mStatePaused = paused;
int stringResourceID = paused ? R.string.text_button_resume :
R.string.text_button_pause;
mPauseButton.setText(stringResourceID);
}
/**
* This is a little helper class that demonstrates simple testing of an
* Expansion APK file delivered by Market. You may not wish to hard-code
* things such as file lengths into your executable... and you may wish to
* turn this code off during application development.
*/
private static class XAPKFile {
public final boolean mIsMain;
public final int mFileVersion;
public final long mFileSize;
XAPKFile(boolean isMain, int fileVersion, long fileSize) {
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
/**
* Here is where you place the data that the validator will use to determine
* if the file was delivered correctly. This is encoded in the source code
* so the application can easily determine whether the file has been
* properly delivered without having to talk to the server. If the
* application is using LVL for licensing, it may make sense to eliminate
* these checks and to just rely on the server.
*/
private static final XAPKFile[] xAPKS = {
new XAPKFile(
true, // true signifies a main file
21, // the version of the APK that the file was uploaded
// against
687801613L // the length of the file in bytes
)
};
/**
* Go through each of the APK Expansion files defined in the structure above
* and determine if the files are present and match the required size. Free
* applications should definitely consider doing this, as this allows the
* application to be launched for the first time without having a network
* connection present. Paid applications that use LVL should probably do at
* least one LVL check that requires the network to be present, so this is
* not as necessary.
*
* #return true if they are present.
*/
boolean expansionFilesDelivered() {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false))
return false;
}
return true;
}
/**
* Calculating a moving average for the validation speed so we don't get
* jumpy calculations for time etc.
*/
static private final float SMOOTHING_FACTOR = 0.005f;
/**
* Used by the async task
*/
private boolean mCancelValidation;
/**
* Go through each of the Expansion APK files and open each as a zip file.
* Calculate the CRC for each file and return false if any fail to match.
*
* #return true if XAPKZipFile is successful
*/
void validateXAPKZipFiles() {
AsyncTask<Object, DownloadProgressInfo, Boolean> validationTask = new AsyncTask<Object, DownloadProgressInfo, Boolean>() {
#Override
protected void onPreExecute() {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_verifying_download);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mCancelValidation = true;
}
});
mPauseButton.setText(R.string.text_button_cancel_verify);
super.onPreExecute();
}
#Override
protected Boolean doInBackground(Object... params) {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(
ApkExpDownloaderActivity.this,
xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(ApkExpDownloaderActivity.this, fileName,
xf.mFileSize, false))
return false;
fileName = Helpers
.generateSaveFileName(ApkExpDownloaderActivity.this, fileName);
ZipResourceFile zrf;
byte[] buf = new byte[1024 * 256];
try {
zrf = new ZipResourceFile(fileName);
ZipEntryRO[] entries = zrf.getAllEntries();
/**
* First calculate the total compressed length
*/
long totalCompressedLength = 0;
for (ZipEntryRO entry : entries) {
totalCompressedLength += entry.mCompressedLength;
}
float averageVerifySpeed = 0;
long totalBytesRemaining = totalCompressedLength;
long timeRemaining;
/**
* Then calculate a CRC for every file in the Zip file,
* comparing it to what is stored in the Zip directory.
* Note that for compressed Zip files we must extract
* the contents to do this comparison.
*/
for (ZipEntryRO entry : entries) {
if (-1 != entry.mCRC32) {
long length = entry.mUncompressedLength;
CRC32 crc = new CRC32();
DataInputStream dis = null;
try {
dis = new DataInputStream(
zrf.getInputStream(entry.mFileName));
long startTime = SystemClock.uptimeMillis();
while (length > 0) {
int seek = (int) (length > buf.length ? buf.length
: length);
dis.readFully(buf, 0, seek);
crc.update(buf, 0, seek);
length -= seek;
long currentTime = SystemClock.uptimeMillis();
long timePassed = currentTime - startTime;
if (timePassed > 0) {
float currentSpeedSample = (float) seek
/ (float) timePassed;
if (0 != averageVerifySpeed) {
averageVerifySpeed = SMOOTHING_FACTOR
* currentSpeedSample
+ (1 - SMOOTHING_FACTOR)
* averageVerifySpeed;
} else {
averageVerifySpeed = currentSpeedSample;
}
totalBytesRemaining -= seek;
timeRemaining = (long) (totalBytesRemaining / averageVerifySpeed);
this.publishProgress(
new DownloadProgressInfo(
totalCompressedLength,
totalCompressedLength
- totalBytesRemaining,
timeRemaining,
averageVerifySpeed)
);
}
startTime = currentTime;
if (mCancelValidation)
return true;
}
if (crc.getValue() != entry.mCRC32) {
Log.e(Constants.TAG,
"CRC does not match for entry: "
+ entry.mFileName);
Log.e(Constants.TAG,
"In file: " + entry.getZipFileName());
return false;
}
} finally {
if (null != dis) {
dis.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return true;
}
#Override
protected void onProgressUpdate(DownloadProgressInfo... values) {
onDownloadProgress(values[0]);
super.onProgressUpdate(values);
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_complete);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
mPauseButton.setText(android.R.string.ok);
} else {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_failed);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
mPauseButton.setText(android.R.string.cancel);
}
super.onPostExecute(result);
}
};
validationTask.execute(new Object());
}
/**
* If the download isn't present, we initialize the download UI. This ties
* all of the controls into the remote service calls.
*/
private void initializeDownloadUI() {
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub
(this, ApkExpDownloaderService.class);
setContentView(R.layout.main);
mPB = (ProgressBar) findViewById(R.id.progressBar);
mStatusText = (TextView) findViewById(R.id.statusText);
mProgressFraction = (TextView) findViewById(R.id.progressAsFraction);
mProgressPercent = (TextView) findViewById(R.id.progressAsPercentage);
mAverageSpeed = (TextView) findViewById(R.id.progressAverageSpeed);
mTimeRemaining = (TextView) findViewById(R.id.progressTimeRemaining);
mDashboard = findViewById(R.id.downloaderDashboard);
mCellMessage = findViewById(R.id.approveCellular);
mPauseButton = (Button) findViewById(R.id.pauseButton);
mWiFiSettingsButton = (Button) findViewById(R.id.wifiSettingsButton);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mStatePaused) {
mRemoteService.requestContinueDownload();
} else {
mRemoteService.requestPauseDownload();
}
setButtonPausedState(!mStatePaused);
}
});
mWiFiSettingsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
}
});
Button resumeOnCell = (Button) findViewById(R.id.resumeOverCellular);
resumeOnCell.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
mRemoteService.requestContinueDownload();
mCellMessage.setVisibility(View.GONE);
}
});
}
/**
* Called when the activity is first create; we wouldn't create a layout in
* the case where we have the file and are moving to another activity
* without downloading.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* Both downloading and validation make use of the "download" UI
*/
initializeDownloadUI();
/**
* Before we do anything, are the files we expect already here and
* delivered (presumably by Market) For free titles, this is probably
* worth doing. (so no Market request is necessary)
*/
if (!expansionFilesDelivered()) {
try {
Intent launchIntent = ApkExpDownloaderActivity.this
.getIntent();
Intent intentToLaunchThisActivityFromNotification = new Intent(
ApkExpDownloaderActivity
.this, ApkExpDownloaderActivity.this.getClass());
intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());
if (launchIntent.getCategories() != null) {
for (String category : launchIntent.getCategories()) {
intentToLaunchThisActivityFromNotification.addCategory(category);
}
}
// Build PendingIntent used to open this activity from
// Notification
PendingIntent pendingIntent = PendingIntent.getActivity(
ApkExpDownloaderActivity.this,
0, intentToLaunchThisActivityFromNotification,
PendingIntent.FLAG_UPDATE_CURRENT);
// Request to start the download
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
pendingIntent, ApkExpDownloaderService.class);
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// The DownloaderService has started downloading the files,
// show progress
initializeDownloadUI();
return;
} // otherwise, download not needed so we fall through to
// starting the movie
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Cannot find own package! MAYDAY!");
e.printStackTrace();
}
} else {
validateXAPKZipFiles();
}
}
/**
* Connect the stub to our service on start.
*/
#Override
protected void onStart() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onStart();
}
/**
* Disconnect the stub from our service on stop
*/
#Override
protected void onStop() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.disconnect(this);
}
super.onStop();
}
//TODO:sp need more info on this
/**
* Critical implementation detail. In onServiceConnected we create the
* remote service and marshaler. This is how we pass the client information
* back to the service so the client can be properly notified of changes. We
* must do this every time we reconnect to the service.
*/
#Override
public void onServiceConnected(Messenger m) {
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
/**
* The download state should trigger changes in the UI --- it may be useful
* to show the state as being indeterminate at times. This sample can be
* considered a guideline.
*/
#Override
public void onDownloadStateChanged(int newState) {
setState(newState);
boolean showDashboard = true;
boolean showCellMessage = false;
boolean paused;
boolean indeterminate;
switch (newState) {
case IDownloaderClient.STATE_IDLE:
// STATE_IDLE means the service is listening, so it's
// safe to start making calls via mRemoteService.
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_CONNECTING:
case IDownloaderClient.STATE_FETCHING_URL:
showDashboard = true;
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_DOWNLOADING:
paused = false;
showDashboard = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_FAILED_CANCELED:
case IDownloaderClient.STATE_FAILED:
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
case IDownloaderClient.STATE_FAILED_UNLICENSED:
paused = true;
showDashboard = false;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
paused = true;
indeterminate = false;
showCellMessage = true;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING:
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_COMPLETED:
showDashboard = false;
paused = false;
indeterminate = false;
validateXAPKZipFiles();
return;
default:
paused = true;
indeterminate = true;
showDashboard = true;
}
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (mDashboard.getVisibility() != newDashboardVisibility) {
mDashboard.setVisibility(newDashboardVisibility);
}
int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
if (mCellMessage.getVisibility() != cellMessageVisibility) {
mCellMessage.setVisibility(cellMessageVisibility);
}
mPB.setIndeterminate(indeterminate);
setButtonPausedState(paused);
}
/**
* Sets the state of the various controls based on the progressinfo object
* sent from the downloader service.
*/
#Override
public void onDownloadProgress(DownloadProgressInfo progress) {
mAverageSpeed.setText(getString(R.string.kilobytes_per_second,
Helpers.getSpeedString(progress.mCurrentSpeed)));
mTimeRemaining.setText(getString(R.string.time_remaining,
Helpers.getTimeRemaining(progress.mTimeRemaining)));
progress.mOverallTotal = progress.mOverallTotal;
mPB.setMax((int) (progress.mOverallTotal >> 8));
mPB.setProgress((int) (progress.mOverallProgress >> 8));
mProgressPercent.setText(Long.toString(progress.mOverallProgress
* 100 /
progress.mOverallTotal) + "%");
mProgressFraction.setText(Helpers.getDownloadProgressString
(progress.mOverallProgress,
progress.mOverallTotal));
}
#Override
protected void onDestroy() {
this.mCancelValidation = true;
super.onDestroy();
}
}
ApkExpReceiver.java
public class ApkExpAlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
try {
DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, ApkExpDownloaderService.class);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
Permissions is a bigger topic and independent of OBB files. Rather than explain it here, I recommend the documentation. In short, for Android devices before 6.0 they only need to be in the manifest. For Android devices after 6.0 you should ask for them at an appropriate moment at runtime.
OBB is as the name suggests an "Opaque" binary blob. You can choose whatever file format you want, google just treats it as a blob of bits. Lots of apps use zip files, but you don't have to, you can use whatever file format you like.
Correct.
The sample download activity is what the name suggests - a sample. You can use that activity, or you can re-use the parts of the code to do the downloading in your own app. Whatever you do, downloading may take a little while, needs permissions, and won't work while offline, so you need to display an appropriate user interface to the user for all these cases. What an appropriate UI is will vary for different apps.
I'm looking for some solution which will be faster than mine. I get a time difference between current time and my saved, next step is to put the time in TextView. This solution works but it makes my app very slowly. Does anyone know faster solution?
Thread counter = new Thread(){
public void run()
{
long saved
long difference;
while(true)
{
saved = Long.parseLong(getString(c, "0", "saved"));// sharedPreferences
difference = System.currentTimeMillis() - saved;
seconds = (int) (difference / 1000) % 60;
difference-=seconds;
minutes= (int)((difference/ 60000 ) % 60);
difference=minutes;
hours = (int) ((difference/ 3600000) % 24);
days = (int) ((difference/86400000) % 30);
difference-=days;
months= (int) ((difference/2592000)/1000);
runOnUiThread(new Runnable() {
#Override
public void run() {
myTextView5.setText(Integer.toString(seconds));
myTextView4.setText(Integer.toString(minutes));
myTextView3.setText(Integer.toString(hours));
myTextView2.setText(Integer.toString(days));
myTextView1.setText(Integer.toString(months));
}
});
Your precision is 1s - so you can Thread.sleep (1000) in the while without missing a beat.
I would recommend the use of JodaTime library.
DateTime saved = //getSaved;
DateTime diff = DateTime.now().minus(saved);
//update TextView
txtView.setText(diff.getSecondsOfMinute().toString());
According to my understanding you are using runOnUiThread that created temporary shortlife objects that is the main reason your code is running slow because in each iteration it does it created a new instance of Runnable class and then updates your UI.
Read this topic to get a good understanding of Communication with Ui Thread
http://developer.android.com/training/multiple-threads/communicate-ui.html
You can use Handlers to overcome this issue all you need to do is pass message to handler and it will do the job for you.
This is how you do it.
First of all create a custom handler class
public class CustomMessageHandler extends Handler {
private IUpdateAudioProgressListView iUpdateRef;
public void setOnProgressUpdatedListner(IUpdateAudioProgressListView ref) {
this.iUpdateRef = ref;
}
#Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
YOUR HANDLING CODE HERE ......................
}
}
then in order to send message to UI thread from a custom thread you need to do following things
initialize a class level variable in your thread class like
public class Test {
CustomMessageHandler handler;
public Test() {
handler = new CustomMessageHandler();
Thread counter = new Thread() {
public void run() {
long saved
long difference;
while (true) {
saved = Long.parseLong(getString(c, "0", "saved"));// sharedPreferences
difference = System.currentTimeMillis() - saved;
seconds = (int) (difference / 1000) % 60;
difference -= seconds;
minutes = (int) ((difference / 60000) % 60);
difference = minutes;
hours = (int) ((difference / 3600000) % 24);
days = (int) ((difference / 86400000) % 30);
difference -= days;
months = (int) ((difference / 2592000) / 1000);
Message msg = handler.obtainMessage();
msg.obj = // send all your variables via a class or whatever logic you use
handler.sendMessage(msg);
}
}
};
}
}
If you don't want to use an external library, I would suggest the use of java.util.Calendar (Don't forget to import that!). For your solution, every time your value in SharedPreferences is changed, just run the code below.
Code to calc diff
Calendar c = Calendar.getInstance();
c.setTimeInMillis(difference);
int months = c.get(Calendar.MONTH);
int days = c.get(Calendar.DAY_OF_MONTH);
int hours = c.get(Calendar.HOUR_OF_DAY);
int minutes = c.get(Calendar.MINUTE);
int seconds = c.get(Calendar.SECOND);
myTextView5.setText(Integer.toString(seconds));
myTextView4.setText(Integer.toString(minutes));
myTextView3.setText(Integer.toString(hours));
myTextView2.setText(Integer.toString(days));
myTextView1.setText(Integer.toString(months));
Detect change in preferences
public class MyPreferences extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// handle the preference change here
}
}
I'm an Android newbie developer and having an issue with a tiny Android app I'm developing which I suspect is related to timing of dynamic view creation. It's a little scorekeeper app and it has dynamically generated player buttons and text fields, defined in a Player class. Here's an excerpt of how I'm doing this:
public Player(String name, int score, int number, boolean enabled, RelativeLayout mainScreen, List<Button> presetButtonList, Editor editor, Context context) {
super();
this.name = name;
this.score = score;
this.number = number;
this.enabled = enabled;
this.editor = editor;
// Get the lowest child on the mainScreen
Integer count = mainScreen.getChildCount(), lowestChildId = null;
Float lowestChildBottom = null;
for (int i = 0; i < count; i++) {
View child = mainScreen.getChildAt(i);
if ((lowestChildId == null || child.getY() > lowestChildBottom) &&
!presetButtonList.contains(child)) {
lowestChildId = child.getId();
lowestChildBottom = child.getY();
}
}
playerNameText = (EditText) setTextViewDefaults(new EditText(context), name);
playerNameText.setSingleLine();
// playerNameText.setMaxWidth(mainScreen.getWidth());
// playerNameText.setEllipsize(TextUtils.TruncateAt.END); //TODO: Prevent names which are too long for screen
playerNameText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable changedText) {
setName(changedText.toString());
updateScreen();
}
public void beforeTextChanged(CharSequence changedText, int start, int count, int after) {}
public void onTextChanged(CharSequence changedText, int start, int before, int count) {}
});
RLParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
if (lowestChildId != null) {
RLParams.addRule(RelativeLayout.BELOW, lowestChildId);
} else {
RLParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
RLParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
}
RLParams.setMargins(0, 10, 0, 10);
mainScreen.addView(playerNameText, RLParams);
the class further defines buttons and etc in a similar fashion, aligning tops with the name text view. I call this when a user clicks a button to add a player, and it works fine, displaying each player below the first on down the screen. The problem comes in when I'm loading a bunch of saved players at the start. Here's where i load the players:
public void loadData() {
RelativeLayout mainScreen = (RelativeLayout)findViewById(R.id.relativeLayout);
playerCount = savedData.getInt("playerCount", playerCount);
Player player;
String name;
playerList.clear();
for (int i=1; i<=playerCount; i++) {
name = savedData.getString("name" + i, null);
if (name != null) {
Log.v("name", name);
player = new Player(name, savedData.getInt("score" + i, 0), i, savedData.getBoolean("enabled" + i, false), mainScreen, presetButtonList, editor, this);
playerList.add(player);
player.updateScreen();
}
}
updateScreen();
}
Finally, I call the loadData() method when the app starts, here:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
savedData = this.getPreferences(Context.MODE_PRIVATE);
editor = savedData.edit();
presetButtonList.add((Button)findViewById(R.id.newGame));
presetButtonList.add((Button)findViewById(R.id.addPlayer));
presetButtonList.add((Button)findViewById(R.id.removePlayer));
loadData();
}
The result? When there are more than two players to load, all the players get loaded to the same spot, on top of Player 2.
I suspect somehow that the players are all being generated at the same time and thus all believing that the lowest player view is Player 1, and not checking each other. I've tried triggering the load later than onCreate() and it still happens. I also tried adding a 3 second sleep within the for loop of loadData() after each player loads to see if that helped, but no luck.
Am I making bad assumptions? What am I doing wrong and how might I fix it?
I suspect what is happening here is that you are attempting to position views that don't have IDs (it looks like you don't set them anywhere in your code). Dynamically created views don't automatically have IDs, so a call to getId() will return NO_ID (See documentation). If you want to use an ID you will have to set it yourself using View.setId().
It was a timing issue after all. I needed to wait for the main screen to load before I could add views to it. Unfortunately, it would never load until all activity on the UI thread was finished.
Thus, I needed threading. I ended up putting loadData() in a thread, like this:
new Thread(new loadDataAsync(this)).start();
class loadDataAsync implements Runnable {
MainActivity mainActivity;
public loadDataAsync(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#Override
public void run() {
try {
Thread.sleep(700);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
loadData(mainActivity);
}
}
Then I created the players back on the UI thread after the initial sleep to give time for the UI to load.
runOnUiThread(new Runnable() {
Player player;
RelativeLayout mainScreen = (RelativeLayout)findViewById(R.id.relativeLayout);
#Override
public void run() {
player = new Player(savedData.getString("name" + playerNum, null), savedData.getInt("score" + playerNum, 0), playerNum, savedData.getBoolean("enabled" + playerNum, false), mainScreen, presetButtonList, editor, mainActivity);
playerList.add(player);
player.updateScreen();
mainScreen.invalidate();
}
});
Not the most elegant solution, but it works for now.
SEE REVISION AT BOTTOM This is a fight card, so it has two people fighting one another, a red vs blue. It has to be a dynamic list that is populated information from parse.com. The first Query is fightOrder. This is a class on Parse.com that has two objectId's on a row. The redCorner and blueCorner find this information in my database (also on parse.com) and display the information accordingly. My problem, is my progressDialog box appears, and it never goes away. My list is never populated. I tried doing it without the dialog box, and populating my list with ever query and had same results.
NOTE: the list is working properly. This is a list I have used successfully before when I would load my information differently. I am just changing the way I load information because I need to have a database of all fighters, and load my fight card from that list.
NOTE: GetCallBack and FindCallBack are asynchronous, that is why this is an odd loop. I have to wait for the done().
Here is the java
public class databaseFightCard extends Activity {
int I;
int size;
private HomeListAdapter HomeListAdapter;
private ArrayList<HomeItem> HomeItemList;
private SeparatedListAdapter adapter;
//this int is to test for main and coMain events. If one is TRUE, It will assign the array position to main or coMain.
int main, coMain;
ParseQuery<ParseObject> blueCorner = ParseQuery.getQuery("FightersDB");
ParseQuery<ParseObject> redCorner = ParseQuery.getQuery("FightersDB");
String name1, name2;
List<String> red = new ArrayList<String>();
List<String> blue = new ArrayList<String>();
private ListView listView;
ProgressDialog progressDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_list);
progressDialog = ProgressDialog.show(this, "", "Loading bout...", true);
initialization();
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
HomeItem homeItem = (HomeItem) adapter.getItem(position);
AlertDialog.Builder showFighter = new AlertDialog.Builder(databaseFightCard.this, android.R.style.Theme_DeviceDefault_Dialog);
showFighter.setTitle(homeItem.getHomeItemLeft().toString() + " and " + homeItem.getHomeItemRight().toString());
showFighter.setMessage("166 - 165\nLogan Utah - Richmond Utah");
showFighter.setPositiveButton("DONE", null);
showFighter.setNegativeButton("Cancel", null);
AlertDialog dialog = showFighter.show();
TextView messageView = (TextView) dialog.findViewById(android.R.id.message);
messageView.setGravity(Gravity.CENTER);
Toast.makeText(getBaseContext(), homeItem.getHomeItemLeft().toString() + " " + homeItem.getHomeItemRight().toString(), Toast.LENGTH_LONG).show();
System.out.println("Selected Item : " + homeItem.getHomeItemID());
}
});
HomeListAdapter = new HomeListAdapter(getApplicationContext(), 0, HomeItemList);
//find the fight card, and read the ids
ParseQuery<ParseObject> fightOrder = ParseQuery.getQuery("FightCard");
fightOrder.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> parseObjects, ParseException e) {
if (e == null) {
size = parseObjects.size();
int i = 0;
while (i < size) {
if (parseObjects.get(i).getBoolean("main")) {
main = i;
}
if (parseObjects.get(i).getBoolean("coMain")) {
coMain = i;
}
red.add(i, parseObjects.get(i).getString("redCorner"));
blue.add(i, parseObjects.get(i).getString("blueCorner"));
i++;
}
displayRed();
} else {
e.printStackTrace();
}
}
});
}
private void displayRed() {
adapter = new SeparatedListAdapter(this);
//find one fighter at a time. in the done() method, start the second fighter.
redCorner.getInBackground(red.get(I), new GetCallback<ParseObject>() {
#Override
public void done(ParseObject parseObject, ParseException e) {
if (e == null) {
HomeItemList = new ArrayList<HomeItem>();
HomeItem homeItem = new HomeItem();
homeItem.setHomeItemID(I);
name1 = parseObject.getString("Name");
homeItem.setHomeItemLeft(name1);
HomeItemList.add(homeItem);
if (HomeListAdapter != null) {
if (I == main) {
adapter.addSection(" MAIN EVENT ", HomeListAdapter);
} else if (I == coMain) {
adapter.addSection(" Co-MAIN EVENT ", HomeListAdapter);
} else {
adapter.addSection(" FIGHT CARD ", HomeListAdapter);
}
}
displayBlue();
} else {
e.printStackTrace();
}
I++;
while (I < size){
displayRed();
}
if (size == I) {
listView.setAdapter(adapter);
progressDialog.dismiss();
}
}
});
}
private void displayBlue() {
//find the red fighters then call the dismiss();
blueCorner.getInBackground(blue.get(I), new GetCallback<ParseObject>() {
#Override
public void done(ParseObject parseObject, ParseException e) {
if (e == null) {
HomeItemList = new ArrayList<HomeItem>();
HomeItem homeItem = new HomeItem();
homeItem.setHomeItemID(I);
name2 = parseObject.getString("Name");
homeItem.setHomeItemLeft(name2);
HomeItemList.add(homeItem);
if (HomeListAdapter != null) {
if (I == main) {
adapter.addSection(" MAIN EVENT ", HomeListAdapter);
} else if (I == coMain) {
adapter.addSection(" Co-MAIN EVENT", HomeListAdapter);
} else {
adapter.addSection(" FIGHT CARD ", HomeListAdapter);
}
}
} else {
e.printStackTrace();
}
//if it is done running through all the IDS, set the listView, and dismiss the dialog.
I++;
while (I < size){
displayRed();
}
if (size == I) {
listView.setAdapter(adapter);
progressDialog.dismiss();
}
}
});
}
private void initialization() {
listView = (ListView) findViewById(R.id.Listview);
}
LogCat
java.lang.RuntimeException: This query has an outstanding network
connection. You have to wait until it's done.
That is pointing to this line:
while (I < size){
displayRed();
}
EDIT
I believe that it is the async tasks that are causing this.
On a previous build: I would call for one line item at a time, add it to my list, repeat until finished, then display list.
On the this build: I want to call for redCorner add it to my list, call blueCorner add it to the same line, repeat until finished, then display the list. Here is what it would look like (previous build):
Revised My question is still unanswered. Maybe I need to simplify it. I will have +-20 objectId's from one class. I took out all the code that is irrelevant. Still getting unexpected results with this code.
redCorner.getInBackground(red.get(i), new GetCallback<ParseObject>() {
#Override
public void done(ParseObject parseObject, ParseException e) {
if (e == null) {
Log.d("NAME " + i, name1 + " ");
i++;
while (i < size) {
redCorner.cancel();
displayRed();
}
if (i == size) {
progressDialog.dismiss();
}
} else {
e.printStackTrace();
}
}
});
This is yet another case of not understanding the nature of Async coding (I've seen a lot of questions with the same issue).
In your case you are calling the displayRed() method that fires off some async code, then returns.
Here's how your code might run:
First call to displayRed() (dr1)
(dr1) Async redCorner.getInBackground(..) (async1) started
(dr1) returns
.. some time passes ..
(async1) getInBackground(..) call returns with data, runs code block
calls displayBlue() (db1)
(db1) blueCorner.getInBackground(..) (async2) started
(db1) returns
begins the while loop
calls displayRed() (dr2)
(dr2) Async redCorner.getInBackground(..) (async3) started
(dr2) nothing has touched I yet, tries to start another async redCorner.getInBackgroud(..) (async4)
ERROR
You're writing your code as if the async blocks are running sync instead. Keep in mind that getInBackground means "make a web call to get this data, and when something happens (error or success) run this block of code I'm giving you, possibly on another thread".
Think about the order you want to achieve things, realise that you're asking it to start a process that takes some time, and adjust your code accordingly.
I want to create a menu which "flips" at random time intervals between 5 children of a viewflipper in random order.
I tried the following code and I can get the System.out.println to display my debugging message to be logged in the logcat at random time intervals so that's working.
However, my screen in the emulator is all black.
When I simply use the setDisplayedChild method in the "onCreate" method with a fixed int, it works fine. Can you help me with this? Thanks many!
public class FlipperTest extends Activity {
int randomTime;
int randomChild;
ViewFlipper fliptest;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_beat_the_game);
ViewFlipper fliptest = (ViewFlipper) findViewById(R.id.menuFlipper);
//this would work
//fliptest.setDisplayedChild(3);
while (true){
try {
Thread.sleep(randomTime);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
Random timerMenu = new Random();
randomTime = timerMenu.nextInt(6) * 2000;
Random childMenu = new Random();
randomChild = childMenu.nextInt(5);
fliptest.setDisplayedChild(randomChild);
System.out.println("executes the finally loop");
}
}
}
Don't block the UI thread like you do with Thread.sleep()(+ infinite loop), instead use a Handler, for example, to make your flips:
private ViewFlipper mFliptest;
private Handler mHandler = new Handler();
private Random mRand = new Random();
private Runnable mFlip = new Runnable() {
#Override
public void run() {
mFliptest.setDisplayedChild(mRand.nextInt());
mHandler.postDelayed(this, mRand.nextInt(6) * 2000);
}
}
//in the onCreate method
mFliptest = (ViewFlipper) findViewById(R.id.menuFlipper);
mHandler.postDelayed(mFlip, randomTime);
Here is my final code. I changed a few things: I put the randomTime int in the run method so that it's updated at each run and therefore the handler gets delayed randomly. Otherwise, it will be delayed according to the first run of the randomly generated number and the time intervals will stay the same. I also had to manipulate the generated randomTime int because if the randomly generated int is 0, then the delay is 0 as well and the flip happens instantly which is not what I wanted.
Thanks a million for your help! :-)
private ViewFlipper fliptest;
private Handler testHandler = new Handler();
private Random mRand = new Random();
private Random timerMenu = new Random();
int randomTime;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
fliptest = (ViewFlipper) findViewById(R.id.menuFlipper);
testHandler.postDelayed(mFlip, randomTime);
}
private Runnable mFlip = new Runnable() {
#Override
public void run() {
randomTime = (timerMenu.nextInt(6) + 1) * 2000;
System.out.println("executes the run method " + randomTime);
fliptest.setDisplayedChild(mRand.nextInt(6));
testHandler.postDelayed(this, (mRand.nextInt(6)+ 1) * 2000);
}
};