Main menu

java.lang.VerifyError - damn you Gógle!

Small change in code. What am I saying?! Adding an exception handler yesterday caused... A CRASH!

Yes, I repeat: adding exception handler caused a crash. java.lang.VerifyError crash!

Very smart indeed...

All because I was trying to catch NetworkOnMainThread, which doesn't exist on older Androids...

Write comment (0 Comments)

The joy of threading

Google has an article on "Painless threading", which I suggest you read. But since I prefer pleasure to "no pain" I needed more than that. Due to the well known fact that Android main (=GUI) thread should be used only for short atomic tasks, using additional threads is in practice a must. When I start programming XenoAmp I began to throw

new Thread(){
  run() {
     // something
  }
}.start();

completely carelesly here and there just at any place that required a background thread. It worked! Then I started adding ThreadPools! Executors! Handlers! Loopers! AsyncTasks! And very soon a pack of lone threads began to swarm like food worms I've raised once on my school sandwich which I forgot to remove from my backpack when school ended. Seriously I was not sure how many threads XenoAmp runs and when, but I was sure I am doing something very wrong.

If your code is similar you should take a moment and find all pieces of code that run in their own threads. Write them down. Now that you see the big picture think if there's some way to group these tasks. For me it was pretty easy, I've observed that:

1) There are tasks that should run in the background but I really don't care how long they take and don't need to know when they finish (writing tags to audio file, clearing old files from disk cache, deleting files, telling 8Tracks about played track, searching for cover on the Internet)

2) There are long tasks that need to run in the background to prepare some important data to be used by main process (audio calibrators, looking for new microphone profiles, getting tags from MusicBrainz, lising ShoutCast, 8Tracks or Subsonic tracks)

3) There are short lasting tasks that have to be run in the background (fetching cover from cache, saving a playlist, receiving play/pause commands)

So when all thready parts were assigned to proper groups I looked to check if these tasks can be scheduled to be executed sequentially in each group. Can cache cleaning wait until some cover is found on the Internet? Sure, doesn't hurt. Group 1) was really easy, so was group 3) as it was assumed it supports only "short, atomic tasks". If they're short, then they're almost simultanious, I'm not going to worry that XenoAmp will react to pause event only after it dumps the playlist to the disk, which is pretty fast. Last step was to think if threads from group 2) should stay there or rather be moved to 1) or 3).

Having that I've created a wrapper around HandlerThread / Looper and added three global instances of it in my Application object:

public class XenoLooper {

	private Handler bkHandler;
	private HandlerThread bkThread;

	public void post(Runnable r) {
		getHandler().post(r);
	}
	
	private Handler getHandler() {
		if (bkThread == null || (bkThread!=null && !bkThread.isAlive()))
		{
			bkThread = new HandlerThread("Xeno background worker"); bkThread.start();
		}
		
		if (bkHandler == null) {	
			bkHandler = new Handler(bkThread.getLooper());
		}

		return bkHandler;
	}

	public void dispose() {
		if(bkHandler!=null)
			bkHandler.removeCallbacksAndMessages(null);
		bkHandler=null;
		
		if(bkThread!=null)
		{
			try{
				bkThread.quit();
				bkThread.interrupt();
				
			} catch (Exception ex) {}
			bkThread=null;
		}
	}
	
	public void flushQueue() {
		if(bkHandler!=null)
			bkHandler.removeCallbacksAndMessages(null);
	}
}

Since I wanted to exit cleanly with no zombie threads, I've added dispose() function. There are also some rare cases when I want to discard queued tasks, thus the flushQueue() function. Now it was enough to obtain a reference to such XenoLopper and post() a Runnable to queue it for running on one of three available and specialized threads. So - thanks to this only "three to rule them all and in the darkness bind 'em". 99% of XenoAmp threads run on these three loopers.

Write comment (0 Comments)

Advanced Android AdapterView / CursorAdapter coding - Part 1

Some info: although this tutorial shows how to create a very specific and probably useless (outside od XenoAmp) AdapterView, it describes advanced concepts that will allow you to create any kind of adapter you can imagine. It seems this is not a common and easily available knowledge, judging from the amount of people looking for readily available code of i.e. HorizontalListView. So - after reading this you will be able to

1) create Adapter that presents data in multidimensional way, instead of standard, linear way
2) create CustomAdapterView that presents your multidimensional data any way you like it, even totally stupid and unordered

It took about 10 iterations to get XenoAmp music view to the place it is now: almost standard CursorAdapter used in almost standard AdapterView. Although first versions of music view were faster, fancier and had more of a WOW! effect due to possibility of zooming from your whole music collection to single tracks, it would ultimately fail with big music collections and would be a nightmare to maintain, as it was just a single View derivative that did all the fancy things.

And yes - at some point it began causing problems for users: it was working too slow, it couldn't properly support quick access index, it was a resource hog. And honestly a piece of crappy code. But if you don't know XenoAmp you have no idea what I am talking aobut, right?

 

XenoAmp music view is a Fragment that shows your music collection in multiple rows, each row can have variable number of items. You can scroll the view up and down, plus you can grab each row left or right. It's like a 2D matrix with variable row length, getting data straight from a view in SQLite database, you can decide what criteria you use for X and Y axii (here you can see publishing date vs album name). You don't have such component in Android! All of existing Adapters are linear! So first attempt at fixing it was creating ArrayAdapter for rows that created HorizontalListView plus ArrayAdapters for items in each row... It was even worse than before in every way imaginable, as you may suspect:

RowAdapter [
  HorizontalListView(RowItemAdapter1 [ Item1, Item2, Item3 ])
  HorizontalListView(RowItemAdapter2 [ Item1 ])
  HorizontalListView(RowItemAdapter3 [ Item1 Item2 ])
  HorizontalListView(RowItemAdapter4 [ Item1 Item2 Item3... ])
  ...
  HorizontalListView(RowItemAdapterN [ ... ])
]

You get it? Scrolling up and down would create a lot of new HorizontalListViews with proper Adapters... OK, I'm too ashamed to talk about it, so let's stop here and see the right way to do it.

Since all Android Adapters are linear (have only one dimension) we have to start with creating our own abstractions for 2D Adapter. Because we can create adapters based on different data stuctures like Cursor or Vector of LinkedLists and because it will be used later by fast scroller, we need this interface first:

public interface MatrixAdapterInterface extends Adapter {
    int getRowCount();
    int getColCount(int row);
    Object getItem(int row, int col);
    View getView(int row, int col, View convertView, ViewGroup parent);
    boolean isEmpty();
}

It's not a real mathematical matrix, as each row can have a variable number of columns (that's why there's a row argument to getColCount method), but still why don't we call it MatrixAdapter, eh? Besides if you need a proper matrix you can easily modify the above, right? convertView in getView method  is a standard Android way of recycling used Views, that means - using pre-inflated Views instead of inflating it every time from xml resource (the Google guys claim it makes things faster and less memory hungry, and they tend to know what they say, so... let's just do what they say...).

Now, since most of the time we would like to use our 2D adapter the same way as a standard Android AdapterView and (even better reason!) because we want to use all goodies that GroupView provides, we have to create generic MatrixAdapterView by deriving from AdapterView. We allow the class to accept only MatrixAdapter, not standard Android adapter, though:

public abstract class MatrixAdapterView extends AdapterView {

    OnSheetItemClickListener mOnItemClickListener;
    OnSheetItemLongClickListener mOnItemLongClickListener;
    
    public MatrixAdapterView(Context context) {
        super(context);
    }

    public MatrixAdapterView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MatrixAdapterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    }

    public interface OnSheetItemClickListener {
        void onItemClick(AdapterView<?> parent, View view, int row, int col, long id);
    }

    public void setOnItemClickListener(OnSheetItemClickListener listener) {
        mOnItemClickListener = listener;
    }

    public final OnSheetItemClickListener getOnRCItemClickListener() {
        return mOnItemClickListener;
    }

    public boolean performItemClick(View view, int row, int col, long id) {
        if (mOnItemClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnItemClickListener.onItemClick(this, view, row, col, id);
            return true;
        }

        return false;
    }

    public interface OnSheetItemLongClickListener {
        boolean onItemLongClick(AdapterView<?> parent, View view, int row, int col, long id);
    }

    public void setOnItemLongClickListener(OnSheetItemLongClickListener listener) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        mOnItemLongClickListener = listener;
    }

    public final OnSheetItemLongClickListener getOnRCItemLongClickListener() {
        return mOnItemLongClickListener;
    }

    public int getRowCount() {
    	return ((MatrixAdapterInterface) getAdapter()).getRowCount();
    }
    
    public int getColCount(int row) {
    	return ((MatrixAdapterInterface) getAdapter()).getColCount(row);
    }
}

This will allow you to set a click and long click listeners for each "cell" in the "matrix" (versus position for linear data) and emit a standard click sound, for each class that you derive from MatrixAdapterView. May look complicated, but that's basicly all this class does.

And that was the easy part. To make it really work you need two more components:

1) Actual adapter (let's assume it's called ChessBoardCursorAdapter) that will bind and map your data to rows and columns, it will implement MatrixAdapterInterface and maybe SectionIndexer and return Views for each row/column.

2) A kind of layout class that will derive from MatrixAdapterView<ChessBoardCursorAdapter> and place the generated Views at proper x,y screen coordinates based on row,column and all scrollers that you would like to implement (for XenoAmp it is horizontal scroller for everything and vertical scrollers for each row).

3) Implement your own FastScroller (if needed), as the default... is not visible for you to use (sheeeesh... what's wrong with you, Google guys?!)

Two first topics will be described in next parts.

Write comment (0 Comments)

Android Translation Helper

If your application has different language versions and a lot of changess to strings.xml (strings added, IDs renamed, strings deleted), the translations will soon become a mess and people won't know anymore what is there left to be translated or if particular string needs translation at all. To fix that I've created a simple java proggy, that

  1. loads English strings as a golden source for all other strings
  2. compares each values-xx/strings.xml to the above
  3. adds a XML comment <!-- PLEASE TRANSLATE --> to each missing value
  4. outputs modified to values-xx/strings.xml_obr, sorted by ID

Fast and easy. Here's the code (note: requires jdom-2.x.x.jar)

 

package pl.qus.utility.ash;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import org.jdom2.Comment;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

public class AndroidStringsHelper {
	private class KeyVal {
		String key;
		String value;
		
		public KeyVal(String k, String v)
		{
			key=k;
			value=v;
		}
	}
	
	String resDir = "";
	ArrayList listaJęzyków = new ArrayList();
	HashMap<String, String> wzorcowaMapa = new HashMap<String, String>();
	HashMap<String, String> obrabianaMapa = new HashMap<String, String>();
	private static String trKey="!#---TRANSLATE---#!";
	
	public AndroidStringsHelper(String resourceDir) {
		String[] listaPlików;
		resDir = resourceDir;

		File katalog = new File(resDir);
		listaPlików = katalog.list();

		for (int i = 0; i < listaPlików.length; i++) {
			File obr = new File(resourceDir,listaPlików[i]);
			if (obr.getName().startsWith("values")) {
				File plik=new File(obr, "strings.xml");
				
				if (plik.exists()) {
					System.out.println("Dodaję:" + obr.getName());

					if (obr.getName().equals("values"))
					{

					}
					else
						listaJęzyków.add(plik);
				}
			}
		}

		obróbJęzyki();
	}

	private void obróbJęzyki() {
		zaczytajPlik(new File(resDir,"values\\strings.xml"),wzorcowaMapa);
		List sortowanaMapa = new ArrayList();
		
		for(File plik:listaJęzyków) {
			sortowanaMapa.clear();
			System.out.println("==================================");
			System.out.println("Obrabiam:"+plik.getPath());
			obrabianaMapa.clear();
			zaczytajPlik(plik,obrabianaMapa);
			
			SortedSet sorted=new TreeSet(wzorcowaMapa.keySet());
			Iterator it = sorted.iterator();
			
			while(it.hasNext()) {
				String klucz=it.next();
				
				if(obrabianaMapa.containsKey(klucz))
				{
					sortowanaMapa.add(new KeyVal(klucz, obrabianaMapa.get(klucz)));
				}
				else
				{
					System.out.println("Brak ["+klucz+"]");
					sortowanaMapa.add(new KeyVal(klucz, trKey+wzorcowaMapa.get(klucz)));
				}
						
			}
			
			zapiszPlik(new File(plik.getPath()+"_obr"),sortowanaMapa);
		}
	}

	private void zapiszPlik(File plik, List docel) {
		FileOutputStream os;
		
		Document profileDoc = new Document();

		Element root=new Element("resources");
		
		Iterator it = docel.iterator();
		while(it.hasNext()) {
			KeyVal nast=it.next();
			String klucz=(String) nast.key;
			String wart=(String) nast.value;
			
			Element nowy=new Element("string");
			if(wart.startsWith(trKey))
			{
				wart=wart.substring(trKey.length());
				root.addContent(new Comment("PLEASE TRANSLATE!"));
			}
			nowy.addContent(wart);
			nowy.setAttribute("name", klucz);
			root.addContent(nowy);
		}
		
		profileDoc.addContent(root);
		
		try {
			os = new FileOutputStream(plik);
			XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
			outputter.output(profileDoc, os);
			os.close();
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void zaczytajPlik(File plik, HashMap<String, String> docel) {
		SAXBuilder builder = new SAXBuilder();

		try {
			Document strings = builder.build(plik);

			Element spis = strings.getRootElement();
			List wszystkie = spis.getChildren();
			for (Element e : wszystkie) {
				docel.put(e.getAttributeValue("name"), e.getText());
			}

		} catch (JDOMException | IOException e) {
			e.printStackTrace();
		}
		System.out.println("Zaczytane:"+docel.size());
	}
	
}

To run it:

public class Main {
	public static void main(String arg[]) {
		new AndroidStringsHelper("/path/to/your/workspace/project/res");
		
	}
}
Write comment (0 Comments)

Run on UI Thread - the painless way

You know there are things in Android that need to run on main thread. There's .runOnUiThread method, that (as usual) is pretty useles, as it exists only in Activity. There's .post method in View(I guess) and for all other issues Google guys will tell you to use AsyncTask, which is ugly and inconvinient beast. OK, sometimes is useful, I do use it, but still hate it. So? Whad do I like to use? Glad you've asked. I use my own runnable:

package pl.qus.xenoamp.helper;

import android.os.Handler;
import android.os.Looper;

public abstract class UIRunnable implements Runnable {

	public abstract void uiRun();
	
	@Override
	public void run() {
		if (Looper.myLooper() == Looper.getMainLooper()) {
			uiRun();
		} else {
			new Handler(Looper.getMainLooper()).post(new Runnable() {
				@Override
				public void run() {
					uiRun();
				}
			});
		}
	}
}

And then in case I want to run something on UI thread, all I have to do is:

new UIRunnable(){
	@Override
	public void uiRun() {
		// this will run on UI thread!
	}}.run();


Better, eh?

Write comment (0 Comments)

FacebookG+TwitterRSS