I am trying to break a String into multiple pages. The string contains a story(hence a bit long one) and I need to load this into an ArrayList of Strings which will be displayed as various pages.
Below is the method in my code which takes in the long string and breaks it into pages and loads the ArrayList of Strings. My problem is that this works pretty fast in Android 3.2. But in my another phone which is 4.3, it loads very slow(say it takes about 10 seconds for what works in 2 seconds in 3.2 one). Below is my code. Can anyone of you please suggest any improvements here in the code which can make it work faster in the new version. Not sure why should new version process slower than the old one.
TextView contentTextView = (TextView)findViewById(R.id.textViewStory1);
String contentString;
TextPaint tp = contentTextView.getPaint();
String[] pages;
int totalPages=0;
ArrayList<String> pagesArray = new ArrayList<String>();
public void loadStory(String storyName){
//initialize variables
numCharsLine=0;
contentString = getStringFromTxtFile(storyName);
int linesInOnePage = getLinesInPage();//get how many lines will be displayed in one page
//load story into arraylist pagesArray
while (contentString != null && contentString.length() != 0)
{
totalPages ++;
int numChars = 0;
int lineCount = 0;
while ((lineCount < linesInOnePage) && (numChars < contentString.length())) {
numCharsLine = tp.breakText(contentString.substring(numChars), true, pageWidth, null);
numChars = numChars + numCharsLine;
lineCount ++;
}
// retrieve the String to be displayed in pagesArray
String toBeDisplayed = contentString.substring(0, numChars);
contentString = contentString.substring(numChars);
pagesArray.add(toBeDisplayed);
numChars = 0;
lineCount = 0;
}
//get the pagecount and reset pageNumber to current page
totalPages=pagesArray.size();
pages = new String[pagesArray.size()];
pages = pagesArray.toArray(pages);
}
Also below is the method for loading the contents of text file into the string
public String getStringFromTxtFile(String fileName) {
try {
InputStream is = getAssets().open(fileName+".txt");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
return new String(buffer);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
For now you are reading whole file to string, then split this string into lines and process it.
I would suggest you to union reading text from file and processing it line by line.
for example, using class LineNumberReader or BufferedReader:
BufferedReader bis = new BufferedReader(new FileReader("my/file/path"));
String line;
while ((line = bis.readLine()) != null) {
processLine(line); //process current line
}
therefore you don't have to do so many concat and substring actions.
Related
I've just wrote my first Android app with Android Studio. It's a vocabulary trainer and it reads in a text file in my assets folder when starting, which contains all the words (until now, I have only ~1000) like this: english$japanese$category. So, I thought this shouldn't be much work, even though I have an old Samsung S2. But It takes like 10 secs to start and sometimes it crashes.
Here's the critical code:
static String word;
static String[] listOfWords;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
readWords();
generateRandomWord();
}
public void readWords() {
try {
InputStream is = getAssets().open("words.txt");
String ww = "";
int data = is.read();
while(data != -1){
ww += (char) data;
data = is.read();
}
listOfWords = ww.split("\n");
} catch (IOException e) {
e.printStackTrace();
}
}
public void generateRandomWord() {
TextView textView = new TextView(this);
textView.setTextSize(40);
textView = (TextView) findViewById(R.id.text_id);
Random random = new Random();
int randomKey = random.nextInt(listOfWords.length-1);
String line = listOfWords[randomKey];
String[] parts = line.split("/");
Log.d("Tango-renshuu", "line: "+line+" "+parts.length+" "+parts[1]);
textView.setText(parts[1]);
word = parts[2];
}
The same thing happens when I try to go back to that Activity from another one, even though I'm using Intent.FLAG_ACTIVITY_CLEAR_TOP Like this:
public void back(View view) {
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
Any idea or do you think its just my device?
Thank
You are reading the asset on the Main-Thread, you need to start a Task to load it, while the Activity is rendered the asset loading happens in background.
Your readWords method is fairly inefficient: you are creating a new string at every loop iteration, and you're reading the file character by character. Consider using a BufferedReader to read the strings line by line directly:
InputStream stream = getAssets().open("words.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
ArrayList<String> lines = new ArrayList<String>();
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
listOfWords = lines.toArray(new String[lines.size()]);
reader.close();
If your code is still too slow after this optimization, then you should move this code to an AsyncTask so at least it doesn't freeze the UI, and you can display a loading spinner in the meantime.
yesterday I published my application which I tested on my phone and worked as intended. When my friends downloaded it using 3G and not WiFi my application failed to download all the content and as a result it crashed. I used a headless fragment which runs an AsyncTask in order to download the content (which is some photos) my guess is that it took a lot of time for some photos and skipped them, throwing some timeOut exception. My question is would this be avoided if instead of an fragment I used a service to run my AsyncTask and download the content?
private ArrayList<Monument> processJsonData(JSONObject jsonObj) throws IOException{
try{
JSONArray posts=jsonObj.getJSONArray(TAG_POSTS);
ArrayList<Monument> monuments = new ArrayList<Monument>();
for (int i=0; i<posts.length(); i++){
JSONArray attachments = c.optJSONArray(TAG_ATTACHMENTS);
if(attachments!=null){
int lengthSize;
if(attachments.length()<3)
lengthSize=attachments.length();
else
lengthSize=3;
for(int j=0;j<lengthSize;++j){
JSONObject atta = attachments.getJSONObject(j);
JSONObject images = atta.optJSONObject(TAG_IMAGES);
if(images!=null){
JSONObject medium = images.getJSONObject(TAG_MEDIUM);
String url_image = medium.getString(TAG_URL_IMAGE);
String id = atta.getString("id");
String filename =title.replace(" ","")+id+".nomedia";
File destination = new File(MyApplication.getPhotoStorage() ,filename);
URL url = new URL (url_image);
InputStream is = url.openStream();
OutputStream os = new FileOutputStream(destination);
byte[] b = new byte[2048];
int length;
while ((length = is.read(b)) != -1) {
os.write(b, 0, length);
}
is.close();
os.close();
localPhotosUrl.add(destination.getAbsolutePath());
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Edit so I made these changes in my code now I'm dealing with the connectionTimeout exception but I can't catch it properly
#Override
protected String doInBackground(String... args) {
JSONParser jParser = new JSONParser();
JSONObject jsonObj = jParser.getJSONFromUrl(url);
Log.d("check1",url);
try {
listOfObjects.addAll(processJsonData(jsonObj));
} catch (Exception e) {
e.printStackTrace();
onDownloadFailed(this);
} finally {
jsonObj=null;
}
return "done";
}
protected void onDownloadFailed(downloadUrl task) {
System.out.println(task.tag+" failed to download");
if(dtask1.cancel(true))
Log.d("TASK1", "Canceled");
if(dtask2.cancel(true))
Log.d("TASK2", "Canceled");
if(dtask3.cancel(true))
Log.d("TASK3", "Canceled");
if(dtask4.cancel(true))
Log.d("TASK4", "Canceled");
mCallbacks.onDownloadFailed();
}
private ArrayList<Monument> processJsonData(JSONObject jsonObj) throws IOException, SocketException, JSONException{
JSONArray attachments = c.optJSONArray(TAG_ATTACHMENTS);
if(attachments!=null){
int lengthSize;
if(attachments.length()<3)
lengthSize=attachments.length();
else
lengthSize=3;
for(int j=0;j<lengthSize;++j){
JSONObject atta = attachments.getJSONObject(j);
JSONObject images = atta.optJSONObject(TAG_IMAGES);
if(images!=null){
JSONObject medium = images.getJSONObject(TAG_MEDIUM);
String url_image = medium.getString(TAG_URL_IMAGE);
String id = atta.getString("id");
String filename =title.replace(" ","")+id+".nomedia";
File destination = new File(MyApplication.getPhotoStorage() ,filename);
try{
URL url = new URL (url_image);
InputStream is = url.openStream();
OutputStream os = new FileOutputStream(destination);
byte[] b = new byte[2048];
int length;
while ((length = is.read(b)) != -1) {
os.write(b, 0, length);
}
is.close();
os.close();
localPhotosUrl.add(destination.getAbsolutePath());
}catch (SocketException e) {
throw new SocketTimeoutException();
}
}
}
}
Alright, so I don't know much about JSON, but that shouldn't affect this answer as a whole I don't think. It looks like your issue could be solved by better use of Exception handling.
At the moment you are not really handling the exception at all, merely catching it and printing the stack trace. Also, because the entire method is inside one try{ } statement the method is exiting if there is a problem dealing with any one of the attachments. Instead, you could include a try{ } block inside of your for loop. This way, if any one of the loop blocks failed (due to say an unstable connection), you can use the catch block to j--; and then Thread.sleep(4000);. That way, when an exception is thrown in the loop it will be caught, the loop will be jumped back to try the same section again, and there will be a pause to to wait for a better connection.
Example (Not tested);
for (int j = 0; j < lengthSize; ++j) {
try{
JSONObject atta = attachments.getJSONObject(j);
JSONObject images = atta.optJSONObject(TAG_IMAGES);
if (images != null) {
JSONObject medium = images.getJSONObject(TAG_MEDIUM);
String url_image = medium.getString(TAG_URL_IMAGE);
String id = atta.getString("id");
String filename = title.replace(" ", "") + id + ".nomedia";
File destination = new File(MyApplication.getPhotoStorage(), filename);
URL url = new URL(url_image);
InputStream is = url.openStream();
OutputStream os = new FileOutputStream(destination);
byte[] b = new byte[2048];
int length;
while ((length = is.read(b)) != -1) {
os.write(b, 0, length);
}
is.close();
os.close();
localPhotosUrl.add(destination.getAbsolutePath());
}
}catch (Exception e){
Thread.sleep(4000);
j--;
}
}
You may also want to create a counter to see how many attempts have been made. If the code makes too many attempts you can then assume that it will never work and return from the method.
I hope this helps. Let me know how you get on.
Also, as an aside you should never really catch Exception. Better to catch the specific exception/s you are expecting so that you can deal with them differently depending on the Exception subtype. You don't want to catch a RunTimeException and try to handle it when there may be no effective way of doing so.
No. This won't be avoided. You cannot expect a connection to last for a long time. Anything can happen on the phone (battery exhausted, network signal lost, etc.)
You must code your app so that it properly checks if it has all the resources available and to retry (or better, resume) downloads that failed.
To increase your chances, break your resources in relatively small downloads instead of a huge file. Then the user does not have to download all from scratch when using 3G for instance.
I am desperatly trying to fix a bug that:
always happens in my emulator for Android versions 2.2, 2.3
never happens in emulator android versions 4.*
never happens in a real device (android 4.*)
It is the following IndexOutOfBoundsException exception:
java.lang.RuntimeException: Unable to start activity
ComponentInfo{<myapppackage>}: java.lang.IndexOutOfBoundsException:
Invalid index 39, size is 0
In my app I am fecthing data from a json file that I am displaying as text. I've isoleted where the bug is coming from, it is when I call this method:
public String getItemValue(int id, String s) {
List<JsonItems> list = new ArrayList<JsonItems>();
try {
// CONVERT RESPONSE STRING TO JSON ARRAY
JSONArray ja = new JSONArray(s);
// ITERATE THROUGH AND RETRIEVE
int n = ja.length();
for (int i = 0; i < n; i++) {
// GET INDIVIDUAL JSON OBJECT FROM JSON ARRAY
JSONObject jo = ja.getJSONObject(i);
// RETRIEVE EACH JSON OBJECT'S FIELDS
JsonItems ji = new JsonItems();
ji.id = jo.getInt("id");
ji.text= jo.getString("text");
list.add(ji);
}
} catch (Exception e) {
e.printStackTrace();
}
return list.get(id).text;
}
My class JsonItems is very basic:
public class JsonItems{
int id;
String text;
}
Sample from my json file:
[
{"id":0,"text":"some text 0"},
{"id":1,"text":"some text 1"},
{"id":2,"text":"some text 2"}
]
Here is how I process content of my json file into a String
public static String fromJsonFileToString(String fileName, Context c) {
//JSONArray jArray = null;
String text = "";
try {
InputStream is = c.getAssets().open(fileName);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
text = new String(buffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
return text;
}
Once again I repeat: the IndexOutOfBoundsException NEVER happens on a device with Android 4.* , it only happens when I test the app on emulators with Android 2.*
Any idea where it is coming from?
Thanks
First problem:
You are not reading your input stream correctly. Calling available literally does just that - it returns you the amount of data that is available to be read, when the call is made. This number may, or may not represent the entire content of the file.
Reading material for you:
How to Read a File in Java.
Writing and Creating Files
Note that there are helper libraries like Apache Commons IO that make it possible to read file contents in a single line of code (IOUtils.toString(inputStream)). Android doesn't support Java 7 yet but a noteworthy alternative is available in that release, with the Files.readAllLines method. In any case, you can make the below shown changes to your file reading code and it should work better:
public static String fromFileToString(String fileName, Context context) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
context.getAssets().open(fileName)));
String line = null;
StringBuilder builder = new StringBuilder(1024);
while ((line = reader.readLine()) != null) {
builder.append(line);
}
return builder.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Second problem:
You do not do any bound check, to make sure that the argument you pass into your 'search' method:
public String getItemValue(int id, String s)
Does not exceed the length of the list of items you eventually calculate:
return list.get(id).text;
// ^
// -------------
// 'id' could be larger than the list size!
In any case, your current design doesn't at all match what you are really trying to do, aka, to determine the element in the JSON array that has an 'id' field matching what you supply to the method. You need to process the JSON data as a map, in order to be able to do this.
public String getItemValue(int id, String json) {
if(json == null || json.trim().equals("")) return null;
Map<Integer, JsonItems> map = new HashMap<Integer, JsonItems>(4);
try {
JSONArray ja = new JSONArray(json);
int n = ja.length();
for (int i = 0; i < n; i++) {
JSONObject jo = ja.getJSONObject(i);
JsonItems ji = new JsonItems();
ji.id = jo.getInt("id");
ji.text = jo.getString("text");
map.put(Integer.valueOf(ji.id, ji);
}
} catch(NumberFormatException nfe) {
throw new IllegalArgumentException("Invalid JSON format");
} catch (Exception e) {
throw new RuntimeException(e);
}
JsonItems item = map.get(id);
return item != null ? item.text : null;
}
Some quick notes:
JsonItems should be called JsonItem, to conform to good Java naming standards
You should really parse and store your JSON just once, to improve performance
You are really only using a minimal subset of your JSON data, you could actually determine the matching node within your for loop and directly return its value, without having to use an intermedia Java bean object
It's my first question, but I'll try to ask it well. Anyway, I'm trying to read data from a text file in Android, but for some reason it keeps returning nothing - namely, IndexOutOfBounds exception. All relevant code:
public Question getNextQuestion(int num) {
Log.d(TAG, array.toString());
return array.get(num+1);
}
public ArrayList<Question> getData(String str) throws NumberFormatException, IOException {
Log.d(TAG, "STARTING getData");
Log.d(TAG, str);
ArrayList<Question> a = new ArrayList<Question>();
String[] strline = str.split("//");
for (int j = 0; j < strline.length; j++) {
if (strline[j] == null) {
strline[j] = "TESTING";
Log.d(TAG, strline[j]);
}
}
int num = 0;
int x = 0;
String y = strline[x];
while (x == 0 || y != null) {
num++;
String[] data = y.split("//");
Log.d(TAG, y);
if (data.length >= 7) {
a.add(new Question(Integer.parseInt(data[0]), data[1], data[2], data[3], data[4], data[5], Integer.parseInt(data[6])));
Log.d(TAG, a.get(a.size()).getText());
}
else if (data.length >= 3) {
a.add(new Question(Integer.parseInt(data[0]), data[1], data[2]));
Log.d(TAG, a.get(a.size()).getText());
}
x++;
if (x < strline.length)
y = strline[x];
else
break;
}
for (int i=0; i<a.size(); i++){
Log.d(TAG, a.get(i).getText());
}
Log.d(TAG, "ENDING getdata");
return a;
}
public ArrayList<Question> openFile() throws IOException {
Log.d(TAG, "STARTING openFile()");
//FileReader fr = new FileReader("data/data/scibowl.prog/questionsb.txt");
FileInputStream fis;
String storedString = "";
try {
fis = openFileInput("questionsb.txt");
DataInputStream dataIO = new DataInputStream(fis);
String strLine = null;
while ((strLine = dataIO.readLine()) != null) {
storedString = storedString + strLine;
}
dataIO.close();
fis.close();
}
catch (Exception e) {
}
array = getData(storedString);
Log.d(TAG, "DID open file, ending openFile()");
return array;
}
This will print in LogCat:
onCreate done
STARTING openFile()
STARTING getData
ENDING getdata
DID open file, ending openFile()
right before Toast.makeText
[]
Caused by: java.lang.IndexOutOfBoundsException: Invalid location 1, size is 0
at java.util.ArrayList.get(ArrayList.java:341)
at scibowl.prog.Round.getNextQuestion(Round.java:55)
at scibowl.prog.RoundView.<init>(RoundView.java:26)
at scibowl.prog.Round.onCreate(Round.java:35)
I've tried practically everything from StringBuffers to FileInputStreams to BufferedReaders, and read the related Android documentation, though feel free to tell me I'm not reading it hard enough. Does anybody know why I keep getting arrays with no elements?
Text file for reference, albeit slightly edited to conform with the blockquote rules:
1//On the standard electromagnetic spectrum, which frequency is between gamma rays and ultraviolet rays?//Radio waves//Microwaves//X-rays//Infrared waves//1
2//Which waves on the electromagnetic spectrum roughly encompasses frequencies of 10^9 Hz to 10^12 Hz?//Visible Light//Gamma Rays//Ultraviolet Rays//Microwaves//3
3//Approximately what percentage of the US’s power comes from coal?//30%//40%//50%//60%//0
4//If a 100-kilogram person starts walking from rest and accelerates to 4.0 m/s over 2 seconds at constant acceleration, how much work does he do?//600 J//800 J//1200 J//2000 J//1
Your logcat suggests it is crashing on line 55 in the method getNextQuestion, which I believe is:
return array.get(num+1);
Also, your logcat suggests that you're trying to access a value in the array that doesn't exist. Meaning you're trying to retrieve the item at index 1 (the second item, I believe), but the size of your array is 0, so there are no items that you can get.
So chances are that in getData you're failing to actually return an ArrayList like you intend. Or the ArrayList you return is empty.
Good all;
I am trying to read some files (from assets) based upon list selections. The code below opens the file and retrieves the content nicely even Arabic letters. the only problem is that the text comes as a very long string without any spaces or newlines. for example the file content is:
first line
second line
third line
the output will be : firstlinesecondlinethirdline
the code is :
public void onItemSelected(AdapterView parent, View v,
int position, long id) {
String filename = getFileName(position);
InputStreamReader ls = null;
try {
AssetManager am = getResources().getAssets();
ls = new InputStreamReader(am.open(filename), "UTF-8");
StringBuffer sb = new StringBuffer();
while( true ) {
int c = ls.read();
if( c < 0 )
break;
if( c >= 32 )
sb.append( (char)c );
selection.setText(new String(sb));
}
} catch(IOException e) {
Log.e(LOG_APP_TAG, e.getMessage());
} finally {
if (ls != null) {
try {
ls.close();
} catch (IOException e) {
Log.e(LOG_APP_TAG, e.getMessage());
}
}
}
}
Your code has several problems :
it is not recommended to use the key word break
using a loop with the condition (true) and breaking it in case of something is very close to "goto programming" and should be avoided.
you could have something like
boolean finished = true;
while( !finished )
{
if( c < 0 )
finished = true;
}//while
This kind of loop is more pleasant to read to my mind, and doesn't "shock" as there is not "endless loop" when you read it.
Moreover, you should really consider using a BufferedReader to read text files. This class will provide you with lines of chars, that would give a simpler code :
BufferedReader bfr = new BufferedReader( new InputStreamReader(am.open(filename), "UTF-8"); );
StringBuffer sb = new StringBuffer();
String read = "";
while( (read=bfr.readLine()) != null )
sb.apennd( read );
BuffereReader will use a buffer to read more than one byte at a time from your file, which is much more efficient.
Btw, you try/catch/finally structure is pretty well done.
Regards,
Stéphane
New line has character value of 10 (<32), so its not appended, so consider appending all characters with >0 value, hence change to
if( c <= 0 )
break;
else
sb.append( (char)c );