Hi im Java\JavaFx\Android-Studio beginner
i wrote a little app in JavaFx, it works fine, now i try to run it on a Smartphone with Android Studio.
Most of this code Works, but i have a problem with my for Loop, it doesnt work, and i dont know why :(
The foundWords List remains empty...
But on my Source Projekt in JavaFx it still works.
public class MainActivity extends AppCompatActivity {
String word;
String list;
char[] wordChars;
List<String> wordList = new ArrayList<String>();
EditText input;
TextView output;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
input = (EditText) findViewById(R.id.input);
output = (TextView) findViewById(R.id.output);
}
/*
Read Textfile
*/
public void readTextfile() {
BufferedReader read = null;
try {
read = new BufferedReader(
new InputStreamReader(getAssets().open("test.txt")));
Arrays.asList(wordList);
while ((list = read.readLine()) != null) {
wordList.add(list);
}
read.close();
} catch (Exception e) {
e.getMessage();
}
Log.i("ListRead", "Done"); }
public static int getNumValue(char[] string, char val) {
int count = 0;
for (char c : string) {
if (c == val) {
count++;
}
}
return count;
}
public static boolean notTooManyLetters(char[] word, char[] wordChars) {
for (char letter : wordChars) {
if (getNumValue(word, letter) > getNumValue(wordChars, letter)) {
return false;
}
}
return true;
}
public static boolean sameLetters(char[] w, char[] wordChars) {
for (char letter : w) {
if (new String(wordChars).indexOf(letter) < 0) {
return false;
}
}
return true;
}
// Create Result
public void compare() {
List<String> foundWords = new ArrayList<String>();
word = input.getText().toString().toUpperCase();
wordChars = word.toCharArray();
for (String w : wordList) {
for (char letter : wordChars) {
if (word.indexOf(letter) > 0 && foundWords.indexOf(w) < 0 && w.length() <= wordChars.length
&& notTooManyLetters(w.toCharArray(), wordChars) && sameLetters(w.toCharArray(), wordChars)) {
foundWords.add(w);
} else {
if (foundWords.size() <= 0) {
Log.i("CheckList", "Empty List!");
}
}
}
}
output.setText(foundWords.toString());
}
public void onKlickAbc(View view) {
readTextfile();
compare();
}}
It seems like your code simply never executes.
Your activity overrides lifecycle methods and those are the only methods the OS is going to call. Any other methods are useless if not linked or triggered from these lifecycle methods.
Indeed, in Android you don't have access to a "main loop" like in other UI frameworks. Please refer to https://developer.android.com/guide/components/activities/intro-activities#mtal
PS: welcome to SO :)
Related
I am fetching data from database. My views are updating only first time when I open the activity. Then when I again open the activity, my views are not updated.(Activity is starting again, hence onCreate() is called again & all settings are same). If I getText() after setting the text, I am getting proper values in log but nothing is displayed in view.
Here is my code snippet:
//My Call Back method
#Override
public void onRatingDataLoaded(ReviewJsonModel review) {
int ratingCount = 0, ownRating = 0;
String averageRating = "0";
if (review != null) {
ratingCount = review.review_count;
DecimalFormat format = new DecimalFormat("##.00");
averageRating = format.format(review.rating);
if (review.ownreviews != null) {
try {
ownRating = Integer.parseInt(review.ownreviews.rating);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
} else {
// do something
}
mTotalRatingCount.setText(String.format(getResources().getString(R.string.review_count), ratingCount));
mAverageRating.setText(averageRating);
// Log.v("LoggingReview", mTotalRatingCount.getText().toString().trim);
myRating.setRating(ownRating);
}
//Here I am setting listner as well as loading data.
public void loadReviewData(RatingDataLoadListener listener, int destinationId) {
if (mDataLoadListener == null)
mDataLoadListener = listener;
new getReviews().execute(destinationId);
}
Next is my asyntask
private class getReviews extends AsyncTask<Integer, Void, ReviewJsonModel> {
#Override
protected ReviewJsonModel doInBackground(Integer... integers) {
Cursor appCursor = mRatingApi.getDestinationReview(integers[0]);
ReviewJsonModel mReviewData = new ReviewJsonModel();
if (appCursor != null && appCursor.getCount() > 0) {
appCursor.moveToFirst();
while (!appCursor.isAfterLast()) {
mReviewData = getDocument(appCursor);
appCursor.moveToNext();
}
appCursor.close();
}
return mReviewData;
}
#Override
protected void onPostExecute(ReviewJsonModel result) {
super.onPostExecute(result);
if (mDataLoadListener != null)
mDataLoadListener.onRatingDataLoaded(result);
}
}
Can't find cause of problem. Any help is appreciated.
Looks like there is callback issue, can you please try below
public void loadReviewData(RatingDataLoadListener listener, int destinationId) {
mDataLoadListener = listener;
new getReviews().execute(destinationId);
}
I have an app, which makes lots of downloads using HttpURLConnection in different threads. The first activity makes 2 threaded requests during onCreate. All works fine on several devices, but now my customer (!) bought a Samsung Tab A SM-T550 with Android 5.0.2.
On this tablet, my app needs minutes for every request, whereas the other devices are need just a second. The customer told me, that the time is wasted until the request reaches the server. (Based on timestamps in the logfile)
Other apps are working well, so my customer blames me for this issue.
Are there any known problems with this device or Android version?
UPDATE:
I got the tablet from my customer today, connected it to my WLAN and it worked well!
But in his office I could see by myself, that the connection was extremely slow, whereas other apps ran quickly.
How is that possible?
The customer has a very fast DSL connection. (50 or 100 MBit - he wasn't sure)
It seems, as if the problem is locally, but I have no idea where to search for a solution...
Here is my code:
public boolean requestHTTPData(TransferResult txResult, String url) {
boolean res = false;
this.txResult = txResult;
try {
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
txResult.setData(in);
txResult.setResult(TransferResult.RESULT_OK);
res = true;
}
catch (Exception e) {
txResult.setError(e.getLocalizedMessage());
}
return res;
}
Here is the code from TranferResult:
public class TransferResult {
public static final int RESULT_OK = 1;
public static final int RESULT_ERROR = -1;
public static final int RESULT_UNDEF = 0;
protected String response = null;
protected int result = RESULT_UNDEF;
protected StringBuffer errorText = null;
protected BufferedReader data = null;
protected Boot[] boote;
protected int startIndex = 0;
protected int anzahl = 0;
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
public int getResult() {
return result;
}
public void setResult(int result) {
this.result = result;
}
public BufferedReader getData() {
return data;
}
public void setData(BufferedReader data) {
this.data = data;
}
public boolean wasSuccessfull() {
return (result == RESULT_OK);
}
public Boot[] getBoote() {
return boote;
}
public void setBoote(Boot[] boote) {
this.boote = boote;
}
public int getStartIndex() {
return startIndex;
}
public void setStartIndex(int startIndex) {
this.startIndex = startIndex;
}
public int getAnzahl() {
return anzahl;
}
public void setAnzahl(int anzahl) {
this.anzahl = anzahl;
}
public void setError(String newErrorText) {
if (newErrorText != null && newErrorText.length() > 0) {
if (errorText == null) {
errorText = new StringBuffer();
} else {
errorText.append("\n");
}
this.errorText.append(errorText);
}
result = RESULT_ERROR;
}
public StringBuffer getErrorText() {
return errorText;
}
public void showError(Context context) {
if (errorText == null) {
setError("Errortext is NULL");
}
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setTitle("");
alertDialog.setMessage(errorText);
alertDialog.show();
}
public TransferResult(Boot[] boote, int startIndex, int anzahl) {
super ();
this.setBoote(boote);
this.setStartIndex(startIndex);
this.setAnzahl(anzahl);
}
public TransferResult() {
this (null, 0, 0);
}
}
Any ideas are welcome.
I have a ParseObject subclass , but everytime I want to get index of it it returns 0 so mListSectionPos returns an array of zero's (hachCode and equals methd implemented thanks to Apache Commons Utils).
It should be String.valueOf(mListItems.indexOf(beer_section)), but instead I'm using mListSectionPos.add(mListItems.indexOf(current_item) - 1); because it's working (more or less). Sometimes it cracks on getCurrentSectionPosition() that also works on indexOf() method.
So my question is: why indexOf() always return 0 in this piece of code?
It's based on https://github.com/bhavyahmehta/ListviewFilter - just adapted for ParseObject lists. Code below is my adaptation of his MainActivity.java that can be found here:
#Override
protected Void doInBackground(ArrayList<PiwoSubclass>... params) {
mListItems.clear();
mListSectionPos.clear();
ArrayList<PiwoSubclass> items = params[0];
if(mItems != null) {
if (mItems.size() > 0) {
String prev_section = "";
for (PiwoSubclass current_item : items) {
if (isCancelled()) break;
String current_section = current_item.getBeerName().substring(0, 1).toUpperCase(Locale.getDefault());
if (!prev_section.equals(current_section)) {
PiwoSubclass beer_section = null;
beer_section = new PiwoSubclass();
beer_section.setBeerName(current_section);
Log.i("ASD-current", beer_section.getBeerName());
mListItems.add(beer_section);
mListItems.add(current_item);
// array list of section positions
mListSectionPos.add(mListItems.indexOf(current_item) - 1); // that want works although it's way around
// TODO why is that error?
Log.i("ASD-listSectionSize", String.valueOf(mListItems.indexOf(beer_section)));
prev_section = current_section;
} else {
mListItems.add(current_item);
}
}
}
}
return null;
}
PiwoSubclass
public class PiwoSubclass extends ParseObject {
private String objectIdP;
private String marka;
private String marka_lowercase;
public PiwoSubclass() {
}
public String getObjectIdfromParse() {
return this.getObjectId();
}
public String getMarka(){
return this.getString("marka");
}
public String getBrewery(){
return this.getString("brewery");
}
public String getBeerName(){
return this.getString("beer_name");
}
public String getMarka_lowercase() {
return this.getString("marka_lowercase");
}
public void setMarka(String value){
put("marka", value);
}
public void setBeerName(String value){
put("beer_name", value);
}
public void setMarka_lowercase(String value){
put("marka_lowercase", value);
}
#Override
public int hashCode() {
return new HashCodeBuilder(17, 31) // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
.append(getObjectIdfromParse())
.toHashCode();
}
#Override
public boolean equals(Object obj) {
//return super.equals(obj);
if (!(obj instanceof PiwoSubclass))
return false;
if (obj == this)
return true;
marka_lowercase = getMarka_lowercase();
PiwoSubclass rhs = (PiwoSubclass) obj;
//Log.i("ASD-subclass", marka + "/" + rhs.getMarka());
return new EqualsBuilder()
// if deriving: appendSuper(super.equals(obj)).
.append(marka_lowercase, rhs.getMarka_lowercase())
.isEquals();
}
Now I have IndexOutOfBounds exception from PinnedHeaderAdapter:
public int getCurrentSectionPosition(int position) {
//String listChar = mListItems.get(position).getBeerName().substring(0, 1).toUpperCase(Locale.getDefault());
PiwoSubclass ps = mListItems.get(position); // TODO errorrrrrrrrr
return mListItems.indexOf(ps);
}
First, you check for mItems
if(mItems != null) {
if (mItems.size() > 0) {
but then you work with items
for (PiwoSubclass current_item : items) {
/* ... */
}
and ignore mItems for the rest of the method. I don't see any connection between these two.
It seems indexOf() doesn't return 0 but 1, otherwise you would get an ArrayList full of -1s
mListSectionPos.add(mListItems.indexOf(current_item) - 1);
I guess, somehow you always check for the first current_item, which is the second element in mListItems. If you would check for the beer_section - as it does for current_section in the original code - the code would work as expected.
After looking into ArrayList.indexOf(), the most likely reason is your PiwoSubclass.equals() method compares always equal to the first non-section element, because it hasn't set a beer name or some similar condition.
So, fixing the equals method might work as well.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 7 years ago.
Improve this question
My app is a medical data viewer, where patients wear a sensor that transmits data by Bluetooth low energy. The app was developed in Objective C, targeting the iOS platform. Now the app needs to be ported to the Android platform.
The current design and implementation for iOS is as follows:
communication - Objective C, specific to the Core Bluetooth API
data/persistence - Objective C, using FMDatabase as the interface to SQLite
algorithms/logic - Objective C
ui - JavaScript/HTML5 based on Phonegap
Since the communication is specific to the Core Bluetooth API, it will have to be re-written for Android. The ui layer should be readily portable without much change as it fully depndendt on Phonegap. Yet for the persistence and logic layers I am looking for a way to either convert them automatically to Android, or re-write them in such a way that they are reusable for both platforms.
What is the best software engineering approach to implement a cross-platform app like this?
Seems like there is:
http://code.google.com/p/objc2j/
The repository should be accesible via http://objc2j.googlecode.com/svn/
Didn't check it myself, so please post your opinion on this.
Google has some open source projects that do this.
You will need to use SVN to access these repositories. Here are the links:
Java to Objective C: http://code.google.com/p/j2objc/
Objective C to Java : http://code.google.com/p/objc2j/
Good luck!
Your best bet is to use Apportable. It's a platform that provides a port of clang, the objective-c runtime, and most of the frameworks on iOS (including UIKit).
There isn't a Core Bluetooth wrapper yet but you can call the java APIs from their platform for that. FMDatabase will work fine and the Phone gap interface should in theory work fine.
I would avoid the code generators suggestions though. They will end up eating a lot of time reimplement everything you already built if you have a type of significate code base.
I've used O2J - Objective-C to Java Converter for a similar scenario and it worked very well.
It will do a great job on your algorithms/logic without much work.
It's customizable so you can add you own translations for your Bluetooth code. You may be able to get by translating the bluetooth method calls directly to java if the APIs work the same but they probably don't. It's best to have a layer of indirection in your Objective-C code for the bluetooth to make it really easy to supply a Android specific implementation. For example create a BluetoothHelper.m and a BluetoothHelper.java and the translation will go much smoother.
I have used it for projects which used FMDatabase. For the FMDatabase part we already have FMDatabase/FMResultSet as the layer of indirection! I implemented FMDatabase/FMResultSet myself since the API for sqlite Objective-c (c based sqlite functions) is too different from Android. O2J helped me get started on translating FMDatabase/FMResultSet and this is what I ended up with...
FMDatabase:
public class FMDatabase
{
private SQLiteDatabase database;
private HashMap<String, SQLiteStatement> compiled;
public FMDatabase(SQLiteDatabase database)
{
this.database = database;
}
public FMResultSet executeQuery_arguments(String sql, Object... args)
{
synchronized (database)
{
String[] selectionArgs = objectArgsAsStrings(args);
Cursor rawQuery = database.rawQuery(sql, selectionArgs);
return new FMResultSet(rawQuery);
}
}
public FMResultSet executeQuery(String sql, Object... args)
{
synchronized (database)
{
String[] selectionArgs = objectArgsAsStrings(args);
Cursor rawQuery = database.rawQuery(sql, selectionArgs);
return new FMResultSet(rawQuery);
}
}
public String debugQuery(String sql, Object...args)
{
StringBuilder sb = new StringBuilder();
FMResultSet rs = executeQuery(sql, args);
rs.setupColumnNames();
HashMap names = rs.columnNameToIndexMap();
Set ks = names.keySet();
for (Object k : ks)
{
sb.append(k);
sb.append("\t");
}
sb.append("\n");
while(rs.next())
{
for (Object k : ks)
{
String key = k.toString();
if(rs.getType(key) == Cursor.FIELD_TYPE_STRING)
{
sb.append(rs.stringForColumn(key));
}
else if(rs.getType(key) == Cursor.FIELD_TYPE_INTEGER)
{
sb.append(rs.longForColumn(key));
}
else if(rs.getType(key) == Cursor.FIELD_TYPE_FLOAT)
{
sb.append(rs.doubleForColumn(key));
}
else if(rs.getType(key) == Cursor.FIELD_TYPE_BLOB)
{
sb.append(rs.stringForColumn(key));
}
else
{
sb.append("<NOT STRING>");
}
sb.append("\t");
}
sb.append("\n");
}
return sb.toString();
}
public String[] objectArgsAsStrings(Object... args)
{
String[] selectionArgs = new String[args.length];
for (int i = 0; i < args.length; i++)
{
Object o = args[i];
if(o instanceof Date)
{
selectionArgs[i] = Long.toString(((Date) o).getTime());
}
else if(o instanceof Boolean)
{
selectionArgs[i] = ((Boolean) o).booleanValue() ? "TRUE" : "FALSE";
}
else
{
selectionArgs[i] = args[i] == null ? "" : o.toString();
}
}
return selectionArgs;
}
public boolean executeUpdate_arguments(String sql, Object... args)
{
synchronized (database)
{
String[] selectionArgs = objectArgsAsStrings(args);
database.execSQL(sql, selectionArgs);
return true;
}
}
public boolean executeUpdate(String sql, Object... args)
{
synchronized (database)
{
SQLiteStatement statement = bindToCachedCompiledStatement(sql, args);
statement.execute();
return true;
}
}
private SQLiteStatement bindToCachedCompiledStatement(String sql, Object... args)
{
HashMap<String, SQLiteStatement> statments = getCompiledStatements();
SQLiteStatement statement = statments.get(sql);
if (statement == null)
{
statement = database.compileStatement(sql);
statments.put(sql, statement);
}
statement.clearBindings();
// bindAllArgsAsStrings(statement, objectArgsAsStrings(args));
bindAllArgs(statement, args);
return statement;
}
private void bindAllArgs(SQLiteStatement statement, Object[] bindArgs)
{
if (bindArgs == null)
{
return;
}
int size = bindArgs.length;
for (int i = 0; i < size; i++)
{
Object arg = bindArgs[i];
int index = i + 1;
if(arg == null)
{
statement.bindNull(index);
}
else if (arg instanceof String)
{
statement.bindString(index, (String) arg);
}
else if (arg instanceof Double || arg instanceof Float)
{
Number numArg = (Number) arg;
statement.bindDouble(index, numArg.doubleValue());
}
else if (arg instanceof Integer || arg instanceof Long)
{
Number numArg = (Number) arg;
statement.bindDouble(index, numArg.longValue());
}
else
{
statement.bindString(index, arg.toString());
}
}
}
public long executeInsert(String string, Object... args)
{
synchronized (database)
{
SQLiteStatement statement = bindToCachedCompiledStatement(string, args);
try
{
return statement.executeInsert();
}
catch (Exception e)
{
Log.i("STD", "No Rows inserted", e);
return 0;
}
}
}
public void bindAllArgsAsStrings(SQLiteStatement statement, String[] bindArgs)
{
if (bindArgs == null)
{
return;
}
int size = bindArgs.length;
for (int i = 0; i < size; i++)
{
statement.bindString(i + 1, bindArgs[i]);
}
}
private HashMap<String, SQLiteStatement> getCompiledStatements()
{
if (compiled == null)
{
compiled = new HashMap<String, SQLiteStatement>();
}
return compiled;
}
public boolean rollback()
{
synchronized (database)
{
database.execSQL("ROLLBACK;");
}
return true;
}
public boolean commit()
{
synchronized (database)
{
database.execSQL("COMMIT;");
}
return true;
}
public boolean beginDeferredTransaction()
{
synchronized (database)
{
database.execSQL("BEGIN DEFERRED TRANSACTION;");
}
return true;
}
public boolean beginTransaction()
{
synchronized (database)
{
database.execSQL("BEGIN EXCLUSIVE TRANSACTION;");
}
return true;
}
public boolean open()
{
return true;
}
public void setShouldCacheStatements(boolean shouldCacheStatements)
{
// TODO
}
}
FMResultSet:
public class FMResultSet
{
private boolean columnNamesSetup;
private HashMap<String, Number> columnNameToIndexMap;
private Cursor rawQuery;
public FMResultSet(Cursor rawQuery)
{
this.rawQuery = rawQuery;
}
public void close()
{
rawQuery.close();
}
public void setupColumnNames()
{
if (columnNameToIndexMap == null)
{
this.setColumnNameToIndexMap(new HashMap());
}
int columnCount = rawQuery.getColumnCount();
int columnIdx = 0;
for (columnIdx = 0; columnIdx < columnCount; columnIdx++)
{
columnNameToIndexMap.put(rawQuery.getColumnName(columnIdx).toLowerCase(), new Integer(columnIdx));
}
columnNamesSetup = true;
}
public boolean next()
{
return rawQuery.moveToNext();
}
public int columnIndexForName(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
columnName = columnName.toLowerCase();
Number n = columnNameToIndexMap.get(columnName);
if (n != null)
{
return NumberValueUtil.intVal(n);
}
Log.i("StdLog", String.format("Warning: I could not find the column named '%s'.", columnName));
return -1;
}
public int intForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return 0;
}
return intForColumnIndex(columnIdx);
}
public int intForColumnIndex(int columnIdx)
{
return rawQuery.getInt(columnIdx);
}
public long longForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return 0;
}
return longForColumnIndex(columnIdx);
}
public long longForColumnIndex(int columnIdx)
{
return (long) rawQuery.getLong(columnIdx);
}
public boolean boolForColumn(String columnName)
{
return (this.intForColumn(columnName) != 0);
}
public boolean boolForColumnIndex(int columnIdx)
{
return (this.intForColumnIndex(columnIdx) != 0);
}
public double doubleForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return 0;
}
return doubleForColumnIndex(columnIdx);
}
public double doubleForColumnIndex(int columnIdx)
{
return rawQuery.getDouble(columnIdx);
}
public String stringForColumnIndex(int columnIdx)
{
return rawQuery.getString(columnIdx);
}
public String stringForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return null;
}
return this.stringForColumnIndex(columnIdx);
}
public Date dateForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return null;
}
return new Date((this.longForColumn(columnName)));
}
public Date dateForColumnIndex(int columnIdx)
{
return new Date((this.longForColumnIndex(columnIdx)));
}
public byte[] dataForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return null;
}
return this.dataForColumnIndex(columnIdx);
}
public byte[] dataForColumnIndex(int columnIdx)
{
return rawQuery.getBlob(columnIdx);
}
public HashMap columnNameToIndexMap()
{
return columnNameToIndexMap;
}
public void setColumnNameToIndexMap(HashMap value)
{
columnNameToIndexMap = value;
}
#SuppressLint("NewApi")
public int getType(String string)
{
return rawQuery.getType(columnIndexForName(string));
}
}
My goal is to have an AsyncTask that
can execute multiple times (one task at a time of course)
its current task can be cancelled
can be used by any activity
can execute many different tasks
does not have any problem with screen rotation (or phonecalls etc)
To achieve that i have created the classes shown below. But my experience with (and understanding of) threads is very limited. And since i don't know of any way to debug multiple threads, there is no way (for me) of knowing if this is going to work or not. So what i'm really asking is: Is this code ok?
And since there is no code that it is currently using this, here's an example use for it:
Data2Get d2g = new Data2Get(this, Data2Get.OpCountNumbers);
d2g.setParam("up2Num", String.valueOf(800));
LongOpsRunner.getLongOpsRunner().runOp(d2g);
So, here we go. This is the interface that every activity that wants to execute a long task (operation - op) should implement:
public interface LongOpsActivity {
public void onTaskCompleted(OpResult result);
}
This is a class to enclose any result of any task:
public class OpResult {
public LongOpsActivity forActivity;
public int opType;
public Object result;
public OpResult(LongOpsActivity forActivity, int opType, Object result){
this.forActivity = forActivity;
this.opType = opType;
this.result = result;
}
}
And finally the big part, the singleton async task class:
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import android.os.AsyncTask;
public class LongOpsRunner extends AsyncTask<Void, OpResult, Void> {
public class Data2Get implements Cloneable {
// one id for each operation
public static final int OpCountNumbers = 1;
public static final int OpCountLetters = 2;
public LongOpsActivity forActivity;
public int opType;
private HashMap<String, String> params = new HashMap<String, String>();
public Data2Get(LongOpsActivity forActivity, int opType) {
this.forActivity = forActivity;
this.opType = opType;
}
public void setParam(String key, String value) {
params.put(key, value);
}
public String getParam(String key) {
return params.get(key);
}
public void clearParams() {
params.clear();
}
#Override
protected Object clone() throws CloneNotSupportedException {
// deep clone
Data2Get myClone = (Data2Get) super.clone();
myClone.clearParams();
for (Entry<String, String> entry : params.entrySet()) {
myClone.setParam(new String(entry.getKey()), new String(entry.getValue()));
}
return myClone;
}
}
private class IntermediateResult extends OpResult {
public IntermediateResult(LongOpsActivity forActivity, int opType, Object result) {
super(forActivity, opType, result);
}
}
// not really needed
private class FinalResult extends OpResult {
public FinalResult(LongOpsActivity forActivity, int opType, Object result) {
super(forActivity, opType, result);
}
}
private final ReentrantLock lock = new ReentrantLock();
private final Condition executeOp = lock.newCondition();
private volatile boolean finished = false;
private volatile boolean waiting = true;
private volatile boolean shouldCancel = false;
private volatile boolean activityHasBeenNotified = true;
private Data2Get startingOpParams = null;
private Data2Get currentOpParams = null;
private FinalResult currentOpResult;
protected Void doInBackground(Void... nothing) {
try {
lock.lockInterruptibly();
do {
waiting = true;
while (waiting) {
executeOp.await();
}
shouldCancel = false;
activityHasBeenNotified = false;
boolean opCancelled = false;
try {
currentOpParams = (Data2Get) startingOpParams.clone();
} catch (CloneNotSupportedException cns) {
// do nothing
}
switch (currentOpParams.opType) {
case Data2Get.OpCountNumbers:
int numberCounter = 0;
int numLoopCount = 0;
while ((!opCancelled) & (numLoopCount <= 5000000)) {
if (!shouldCancel) {
numberCounter = (numberCounter + 1)
% Integer.parseInt(currentOpParams.getParam("up2Num"));
if (numberCounter == 0) {
numLoopCount++;
publishProgress(new IntermediateResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Numbers loop count:" + numLoopCount));
}
} else {
opCancelled = true;
activityHasBeenNotified = true;
}
if (!opCancelled) {
currentOpResult = new FinalResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Numbers loop completed.");
publishProgress(currentOpResult);
}
}
break;
case Data2Get.OpCountLetters:
int letterLoopCount = 0;
char ch = 'a';
while (!opCancelled & (letterLoopCount <= 5000000)) {
if (!shouldCancel) {
ch++;
if (Character.toString(ch).equals(currentOpParams.getParam("up2Letter"))) {
ch = 'a';
letterLoopCount++;
publishProgress(new IntermediateResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Letters loop count:" + letterLoopCount));
}
} else {
opCancelled = true;
activityHasBeenNotified = true;
}
if (!opCancelled) {
currentOpResult = new FinalResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Letters loop completed.");
publishProgress(currentOpResult);
}
}
break;
default:
}
} while (!finished);
lock.unlock();
} catch (InterruptedException e) {
// do nothing
}
return null;
}
public void cancelCurrentOp() {
shouldCancel = true;
}
#Override
protected void onProgressUpdate(OpResult... res) {
OpResult result = res[0];
if (result instanceof IntermediateResult) {
// normal progress update
// use result.forActivity to show something in the activity
} else {
notifyActivityOpCompleted(result);
}
}
public boolean currentOpIsFinished() {
return waiting;
}
public void runOp(Data2Get d2g) {
// Call this to run an operation
// Should check first currentOpIsFinished() most of the times
startingOpParams = d2g;
waiting = false;
executeOp.signal();
}
public void terminateAsyncTask() {
// The task will only finish when we call this method
finished = true;
lock.unlock(); // won't this throw an exception?
}
protected void onCancelled() {
// Make sure we clean up if the task is killed
terminateAsyncTask();
}
// if phone is rotated, use setActivity(null) inside
// onRetainNonConfigurationInstance()
// and setActivity(this) inside the constructor
// and all that only if there is an operation still running
public void setActivity(LongOpsActivity activity) {
currentOpParams.forActivity = activity;
if (currentOpIsFinished() & (!activityHasBeenNotified)) {
notifyActivityOpCompleted(currentOpResult);
}
}
private void notifyActivityOpCompleted(OpResult result) {
if (currentOpParams.forActivity != null) {
currentOpParams.forActivity.onTaskCompleted(result);
activityHasBeenNotified = true;
}
}
private static LongOpsRunner ref;
private LongOpsRunner() {
this.execute();
}
public static synchronized LongOpsRunner getLongOpsRunner() {
if (ref == null)
ref = new LongOpsRunner();
return ref;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
I hope someone helps with making this work, as it would be very useful not only for me, but many other people out there. Thank you.
Try Loaders. I switched from simple AsyncTasks to AsyncTaskLoaders and they solve lots of problems. If you implement a Loader as a standalone class, it would meet all of your requirements, especially when it comes to rotation which is the biggest issue with old AsyncTask.