Threads and UI in Android

Discussion in 'Java' started by Dirk Bruere at NeoPax, Apr 3, 2011.

  1. Part of the problem I have been having with adding data to a listview
    stems from the threads I have running. Specifically, I have a thread
    that constantly checks for messages coming over the Wifi/LAN. When it
    finds one it loads the datagram, then calls a static method from a class
    BlinkAPI which calls other BlinkAPI methods which loads the data into
    arrays.

    So far so good - no problems.
    However, if the Android ListView in the UI is to be updated with this
    data there is a problem. I cannot go in and do stuff to the ListView
    from BlinkAPI or I get a message about "called from wrong thread".

    Given that loading up Listviews, adapters etc should be done from the
    same UI thread, how best to put a "break" between my LAN thread and the
    UI thread?

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 3, 2011
    #1
    1. Advertising

  2. On 03/04/2011 19:16, Dirk Bruere at NeoPax wrote:
    > Part of the problem I have been having with adding data to a listview
    > stems from the threads I have running. Specifically, I have a thread
    > that constantly checks for messages coming over the Wifi/LAN. When it
    > finds one it loads the datagram, then calls a static method from a class
    > BlinkAPI which calls other BlinkAPI methods which loads the data into
    > arrays.
    >
    > So far so good - no problems.
    > However, if the Android ListView in the UI is to be updated with this
    > data there is a problem. I cannot go in and do stuff to the ListView
    > from BlinkAPI or I get a message about "called from wrong thread".
    >
    > Given that loading up Listviews, adapters etc should be done from the
    > same UI thread, how best to put a "break" between my LAN thread and the
    > UI thread?
    >


    It would seem the answer is in here somewhere
    http://developer.android.com/resources/articles/painless-threading.html

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 3, 2011
    #2
    1. Advertising

  3. In message <>, Dirk Bruere at NeoPax wrote:

    > It would seem the answer is in here somewhere
    > http://developer.android.com/resources/articles/painless-threading.html


    Seems like AsyncTask is the way to go: it automatically divides up execution
    between a background thread and the UI thread, and takes care of all the
    synchronization between the two for you.

    How much more painless can you get? :)
     
    Lawrence D'Oliveiro, Apr 3, 2011
    #3
  4. Dirk Bruere at NeoPax

    markspace Guest

    On 4/3/2011 12:27 PM, Dirk Bruere at NeoPax wrote:

    >> So far so good - no problems.
    >> However, if the Android ListView in the UI is to be updated with this
    >> data there is a problem. I cannot go in and do stuff to the ListView
    >> from BlinkAPI or I get a message about "called from wrong thread".



    This seems analogus to Java Swing & EDT. Use the Activity.runOnUiThread
    method to send processing to the UI thread, so you won't get this message.

    Given:

    some other thread
    |
    |
    V
    public void someMethod( Params.... ) {
    // do set up

    // do this on UI thread

    // do clean up
    }

    Transform this to:

    some other thread
    |
    |
    V
    public void someMethod( Params.... ) {
    // do set up

    Activity.runOnUiThread( new Runnable() {
    public void run() {
    // do this on UI thread
    }
    } );

    // do clean up
    }

    To run the center bit of code on the UI thread. Note that you are
    executing code asynchronously and the "clean up" will likely happen
    before the UI thread bit. Something to be aware of.
     
    markspace, Apr 4, 2011
    #4
  5. Dirk Bruere at NeoPax

    markspace Guest

    On 4/3/2011 3:16 PM, Lawrence D'Oliveiro wrote:
    > In message<>, Dirk Bruere at NeoPax wrote:
    >
    >> It would seem the answer is in here somewhere
    >> http://developer.android.com/resources/articles/painless-threading.html

    >
    > Seems like AsyncTask is the way to go: it automatically divides up execution
    > between a background thread and the UI thread, and takes care of all the
    > synchronization between the two for you.
    >
    > How much more painless can you get? :)




    Given that the OP want's to "constantly [check] for messages" I think
    the way he's doing it now is fine. runOnUiThread() does the
    synchronization that he needs, and it seems cleaner than trying to wrap
    his deamon in an AsyncTask object and constantly call publishProgress()
    whenever he gets a packet.

    Modern threading is getting sophisticated, and one sophisticated thing
    to do is to limit the number of threads so as to not overwhelm the OS.
    Limited thread pools are used for this purpose. I don't know how
    AsynchTask is implemented, but if it uses a thread pool, you could
    easily starve your own app of threads for AsynchTask by permanently
    capturing too many AsynchTask threads, which is what the OP's task would do.

    "Long running" tasks relative to the UI response time go in AsynchTask.
    "Really long running" tasks, or permanent tasks like the OP's little
    network daemon, go in their own private thread. Seems safest that way.
     
    markspace, Apr 4, 2011
    #5
  6. In article <inb4mo$2br$>, markspace <-@.> wrote:

    > On 4/3/2011 3:16 PM, Lawrence D'Oliveiro wrote:
    > > In message<>, Dirk Bruere at NeoPax wrote:
    > >
    > >> It would seem the answer is in here somewhere
    > >> http://developer.android.com/resources/articles/painless-threading.html

    > >
    > > Seems like AsyncTask is the way to go: it automatically divides up execution
    > > between a background thread and the UI thread, and takes care of all the
    > > synchronization between the two for you.
    > >
    > > How much more painless can you get? :)

    >
    > Given that the OP want's to "constantly [check] for messages" I think
    > the way he's doing it now is fine. runOnUiThread() does the
    > synchronization that he needs, and it seems cleaner than trying to
    > wrap his deamon in an AsyncTask object and constantly call
    > publishProgress() whenever he gets a packet.
    >
    > Modern threading is getting sophisticated, and one sophisticated
    > thing to do is to limit the number of threads so as to not overwhelm
    > the OS. Limited thread pools are used for this purpose. I don't know
    > how AsynchTask is implemented, but if it uses a thread pool, you
    > could easily starve your own app of threads for AsynchTask by
    > permanently capturing too many AsynchTask threads, which is what the
    > OP's task would do.
    >
    > "Long running" tasks relative to the UI response time go in
    > AsynchTask. "Really long running" tasks, or permanent tasks like the
    > OP's little network daemon, go in their own private thread. Seems
    > safest that way.


    I have no practical experience on android, but I was struck by the
    similarity between AsyncTask and SwingWorker:

    <http://developer.android.com/reference/android/os/AsyncTask.html>
    <http://download.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.htm
    l>

    It appears to be of more recent vintage, and it supports either a
    SERIAL_EXECUTOR or THREAD_POOL_EXECUTOR, with certain caveats for the
    latter.

    --
    John B. Matthews
    trashgod at gmail dot com
    <http://sites.google.com/site/drjohnbmatthews>
     
    John B. Matthews, Apr 4, 2011
    #6
  7. On 04/04/2011 01:51, markspace wrote:
    > On 4/3/2011 3:16 PM, Lawrence D'Oliveiro wrote:
    >> In message<>, Dirk Bruere at NeoPax wrote:
    >>
    >>> It would seem the answer is in here somewhere
    >>> http://developer.android.com/resources/articles/painless-threading.html

    >>
    >> Seems like AsyncTask is the way to go: it automatically divides up
    >> execution
    >> between a background thread and the UI thread, and takes care of all the
    >> synchronization between the two for you.
    >>
    >> How much more painless can you get? :)

    >
    >
    >
    > Given that the OP want's to "constantly [check] for messages" I think
    > the way he's doing it now is fine. runOnUiThread() does the
    > synchronization that he needs, and it seems cleaner than trying to wrap
    > his deamon in an AsyncTask object and constantly call publishProgress()
    > whenever he gets a packet.
    >
    > Modern threading is getting sophisticated, and one sophisticated thing
    > to do is to limit the number of threads so as to not overwhelm the OS.
    > Limited thread pools are used for this purpose. I don't know how
    > AsynchTask is implemented, but if it uses a thread pool, you could
    > easily starve your own app of threads for AsynchTask by permanently
    > capturing too many AsynchTask threads, which is what the OP's task would
    > do.
    >
    > "Long running" tasks relative to the UI response time go in AsynchTask.
    > "Really long running" tasks, or permanent tasks like the OP's little
    > network daemon, go in their own private thread. Seems safest that way.
    >


    Currently I have this:

    public class BlinkDataThread extends Thread
    {
    int length = Constants.PACKET_LENGTH;
    byte[] receiveBuffer = new byte[length];
    DatagramSocket socket;
    DatagramPacket packet;

    public void run()
    {
    try
    {
    DatagramSocket ds = new DatagramSocket(Constants.LOCAL_PORT);
    DatagramPacket incoming = new DatagramPacket(receiveBuffer,
    receiveBuffer.length);
    incoming.setLength(length);
    String packetStr=new String(receiveBuffer, "UTF-8");

    while(true) //Run this as an endless loop
    {
    ds.setReceiveBufferSize(receiveBuffer.length);
    ds.receive(incoming);
    packetStr = new String(receiveBuffer, 0,
    incoming.getLength(), "UTF-8");
    if (packetStr != null) BlinkAPI.updateIncomingData(packetStr);
    }
    } catch (IOException e1) {}
    }

    }

    Where linkAPI.updateIncomingData(packetStr); is the offending bit since
    it triggers updates on the UI thread.

    That would be where I use runOnUiThread()?

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 4, 2011
    #7
  8. In message <>, Dirk Bruere at NeoPax wrote:

    > Where linkAPI.updateIncomingData(packetStr); is the offending bit since
    > it triggers updates on the UI thread.
    >
    > That would be where I use runOnUiThread()?


    OK, if it’s running in an endless loop, I guess that’s not suitable for an
    AsyncTask.

    I believe the recommended mechanism for posting actions back to the UI
    thread is via a Handler
    <http://developer.android.com/reference/android/os/Handler.html>: create
    this from the UI thread, pass it to the background thread, and the latter
    can use the post method to send back a Runnable to be executed.

    By the way, note that faceless background threads can be killed at any time.
     
    Lawrence D'Oliveiro, Apr 4, 2011
    #8
  9. On 04/04/2011 06:23, Dirk Bruere at NeoPax wrote:
    > On 04/04/2011 01:51, markspace wrote:
    >> On 4/3/2011 3:16 PM, Lawrence D'Oliveiro wrote:
    >>> In message<>, Dirk Bruere at NeoPax
    >>> wrote:
    >>>
    >>>> It would seem the answer is in here somewhere
    >>>> http://developer.android.com/resources/articles/painless-threading.html
    >>>
    >>> Seems like AsyncTask is the way to go: it automatically divides up
    >>> execution
    >>> between a background thread and the UI thread, and takes care of all the
    >>> synchronization between the two for you.
    >>>
    >>> How much more painless can you get? :)

    >>
    >>
    >>
    >> Given that the OP want's to "constantly [check] for messages" I think
    >> the way he's doing it now is fine. runOnUiThread() does the
    >> synchronization that he needs, and it seems cleaner than trying to wrap
    >> his deamon in an AsyncTask object and constantly call publishProgress()
    >> whenever he gets a packet.
    >>
    >> Modern threading is getting sophisticated, and one sophisticated thing
    >> to do is to limit the number of threads so as to not overwhelm the OS.
    >> Limited thread pools are used for this purpose. I don't know how
    >> AsynchTask is implemented, but if it uses a thread pool, you could
    >> easily starve your own app of threads for AsynchTask by permanently
    >> capturing too many AsynchTask threads, which is what the OP's task would
    >> do.
    >>
    >> "Long running" tasks relative to the UI response time go in AsynchTask.
    >> "Really long running" tasks, or permanent tasks like the OP's little
    >> network daemon, go in their own private thread. Seems safest that way.
    >>

    >
    > Currently I have this:
    >
    > public class BlinkDataThread extends Thread
    > {
    > int length = Constants.PACKET_LENGTH;
    > byte[] receiveBuffer = new byte[length];
    > DatagramSocket socket;
    > DatagramPacket packet;
    >
    > public void run()
    > {
    > try
    > {
    > DatagramSocket ds = new DatagramSocket(Constants.LOCAL_PORT);
    > DatagramPacket incoming = new DatagramPacket(receiveBuffer,
    > receiveBuffer.length);
    > incoming.setLength(length);
    > String packetStr=new String(receiveBuffer, "UTF-8");
    >
    > while(true) //Run this as an endless loop
    > {
    > ds.setReceiveBufferSize(receiveBuffer.length);
    > ds.receive(incoming);
    > packetStr = new String(receiveBuffer, 0, incoming.getLength(), "UTF-8");
    > if (packetStr != null) BlinkAPI.updateIncomingData(packetStr);
    > }
    > } catch (IOException e1) {}
    > }
    >
    > }
    >
    > Where linkAPI.updateIncomingData(packetStr); is the offending bit since
    > it triggers updates on the UI thread.
    >
    > That would be where I use runOnUiThread()?
    >


    Or not.
    That BlinkAPI.updateIncomingData(packetStr) eventually gets to call this
    from the Data class

    public static void setRadioTitleAdapterListView()
    {
    radioLV.setAdapter(radioTitleAdapter);
    radioTitleAdapter.notifyDataSetChanged();
    radioLV.setVisibility(VISIBLE);
    }
    It is really this I need to execute in the UI thread.
    Is there some way of wrapping that method (amongst several of the same
    type) as a message to the UI thread telling it to execute
    Data.setRadioTitleAdapterListView() from there?

    [I can send the packet received in the BlinkDataThread but that rather
    defeats the idea since all the heavy duty processing occurs after it is
    picked up.]

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 4, 2011
    #9
  10. Dirk Bruere at NeoPax

    markspace Guest

    On 4/4/2011 11:18 AM, Dirk Bruere at NeoPax wrote:

    > Or not.
    > That BlinkAPI.updateIncomingData(packetStr) eventually gets to call this
    > from the Data class
    >
    > public static void setRadioTitleAdapterListView()
    > {



    I don't like the idea that you're suggesting here. Hard baking this
    method so it always runs on the UI thread sounds bad. You want to keep
    methods flexible so they can be used in a variety of contexts. Too much
    specialized behavior results in classes that require too many support
    classes or a specialized environment to function at all.

    Better to do it where you had it at first. You're already processing
    the packet and making a string. It seems fine to allow your Blink
    routine to process the resulting string on the UI thread. You don't
    have to be manic about removing every last cycle from the UI thread,
    just keep long tasks (like IO, or sorting or searching) away from it.

    Also, in the code you posted, you set up packetStr once outside of the
    loop, then never use that value, so I'm moving packetStr inside the loop
    and removing the unused assignment.

    And I think you mean !packetStr.equals( "" ), not packetStr != null. It
    isn't possible to get null from a constructor.


    public void run()
    {
    try
    {
    DatagramSocket ds = new DatagramSocket(Constants.LOCAL_PORT);
    DatagramPacket incoming = new DatagramPacket(
    receiveBuffer, receiveBuffer.length);
    incoming.setLength(length);
    // not used String packetStr=new String(receiveBuffer, "UTF-8");

    while(true) //Run this as an endless loop
    {
    ds.setReceiveBufferSize(receiveBuffer.length);
    ds.receive(incoming);
    final String packetStr = new String(receiveBuffer, 0,
    incoming.getLength(), "UTF-8");
    if( !packetStr.equals( "" ) ) {
    Activity.runOnUiThread( new Runnable() {
    public void run() {
    BlinkAPI.updateIncomingData(packetStr);
    }
    } );
    }
    } catch (IOException e1) {
    // you really must log errors if you find them
    // honestly, you'll be happy you did.
    }
    }
    }

    At least we're doing things now that look like actual Java programming.
     
    markspace, Apr 4, 2011
    #10
  11. On 04/04/2011 22:54, markspace wrote:
    > On 4/4/2011 11:18 AM, Dirk Bruere at NeoPax wrote:
    >
    >> Or not.
    >> That BlinkAPI.updateIncomingData(packetStr) eventually gets to call this
    >> from the Data class
    >>
    >> public static void setRadioTitleAdapterListView()
    >> {

    >
    >
    > I don't like the idea that you're suggesting here. Hard baking this
    > method so it always runs on the UI thread sounds bad. You want to keep
    > methods flexible so they can be used in a variety of contexts. Too much
    > specialized behavior results in classes that require too many support
    > classes or a specialized environment to function at all.
    >
    > Better to do it where you had it at first. You're already processing the
    > packet and making a string. It seems fine to allow your Blink routine to
    > process the resulting string on the UI thread. You don't have to be
    > manic about removing every last cycle from the UI thread, just keep long
    > tasks (like IO, or sorting or searching) away from it.
    >
    > Also, in the code you posted, you set up packetStr once outside of the
    > loop, then never use that value, so I'm moving packetStr inside the loop
    > and removing the unused assignment.
    >
    > And I think you mean !packetStr.equals( "" ), not packetStr != null. It
    > isn't possible to get null from a constructor.
    >
    >
    > public void run()
    > {
    > try
    > {
    > DatagramSocket ds = new DatagramSocket(Constants.LOCAL_PORT);
    > DatagramPacket incoming = new DatagramPacket(
    > receiveBuffer, receiveBuffer.length);
    > incoming.setLength(length);
    > // not used String packetStr=new String(receiveBuffer, "UTF-8");
    >
    > while(true) //Run this as an endless loop
    > {
    > ds.setReceiveBufferSize(receiveBuffer.length);
    > ds.receive(incoming);
    > final String packetStr = new String(receiveBuffer, 0,
    > incoming.getLength(), "UTF-8");
    > if( !packetStr.equals( "" ) ) {
    > Activity.runOnUiThread( new Runnable() {
    > public void run() {
    > BlinkAPI.updateIncomingData(packetStr);
    > }
    > } );
    > }
    > } catch (IOException e1) {
    > // you really must log errors if you find them
    > // honestly, you'll be happy you did.
    > }
    > }
    > }
    >
    > At least we're doing things now that look like actual Java programming.


    :)

    BTW, do you any kind of feel for how long it would take to load a
    ListView (and adapter/array) with (say) 1000 items comprising about
    100kB of data?

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 4, 2011
    #11
  12. Dirk Bruere at NeoPax

    markspace Guest

    On 4/4/2011 3:41 PM, Dirk Bruere at NeoPax wrote:

    > BTW, do you any kind of feel for how long it would take to load a
    > ListView (and adapter/array) with (say) 1000 items comprising about
    > 100kB of data?



    Here's a different sort of question: do you know of any users who are
    actually going to use a list view with 1000 items in it?

    Most UIs implement some sort of pagination and load when the user is
    ready for the next batch. 10-50 at a time is normal.
     
    markspace, Apr 5, 2011
    #12
  13. On 05/04/2011 00:29, markspace wrote:
    > On 4/4/2011 3:41 PM, Dirk Bruere at NeoPax wrote:
    >
    >> BTW, do you any kind of feel for how long it would take to load a
    >> ListView (and adapter/array) with (say) 1000 items comprising about
    >> 100kB of data?

    >
    >
    > Here's a different sort of question: do you know of any users who are
    > actually going to use a list view with 1000 items in it?


    Yes

    > Most UIs implement some sort of pagination and load when the user is
    > ready for the next batch. 10-50 at a time is normal.


    Well, there will be a couple of buttons to jump by 10% and 1% through
    the list

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 5, 2011
    #13
  14. Dirk Bruere at NeoPax

    markspace Guest

    On 4/4/2011 4:32 PM, Dirk Bruere at NeoPax wrote:
    > On 05/04/2011 00:29, markspace wrote:
    >> On 4/4/2011 3:41 PM, Dirk Bruere at NeoPax wrote:
    >>
    >>> BTW, do you any kind of feel for how long it would take to load a
    >>> ListView (and adapter/array) with (say) 1000 items comprising about
    >>> 100kB of data?

    >>
    >>
    >> Here's a different sort of question: do you know of any users who are
    >> actually going to use a list view with 1000 items in it?

    >
    > Yes



    Seriously? That's crazy.

    I'd just load the 1000, then check the response. (You know, measure
    performance?) If it's too long, split the load into groups of 50 or 100
    or so, or whatever gives better updates and responsiveness for the UI.
     
    markspace, Apr 5, 2011
    #14
  15. On 05/04/2011 01:27, markspace wrote:
    > On 4/4/2011 4:32 PM, Dirk Bruere at NeoPax wrote:
    >> On 05/04/2011 00:29, markspace wrote:
    >>> On 4/4/2011 3:41 PM, Dirk Bruere at NeoPax wrote:
    >>>
    >>>> BTW, do you any kind of feel for how long it would take to load a
    >>>> ListView (and adapter/array) with (say) 1000 items comprising about
    >>>> 100kB of data?
    >>>
    >>>
    >>> Here's a different sort of question: do you know of any users who are
    >>> actually going to use a list view with 1000 items in it?

    >>
    >> Yes

    >
    >
    > Seriously? That's crazy.


    List of 1000 videos, in alphabetical order

    > I'd just load the 1000, then check the response. (You know, measure
    > performance?) If it's too long, split the load into groups of 50 or 100
    > or so, or whatever gives better updates and responsiveness for the UI.


    I will do, but I don't have the real database to hand.
    I've tested it with 85 and it seems to take about 0.2 seconds, which is
    just a guess.

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 5, 2011
    #15
  16. Dirk Bruere at NeoPax

    markspace Guest

    On 4/3/2011 11:48 PM, Lawrence D'Oliveiro wrote:
    > In message<>, Dirk Bruere at NeoPax wrote:
    >
    >> Where linkAPI.updateIncomingData(packetStr); is the offending bit since
    >> it triggers updates on the UI thread.
    >>
    >> That would be where I use runOnUiThread()?

    >
    > OK, if it’s running in an endless loop, I guess that’s not suitable for an
    > AsyncTask.
    >
    > I believe the recommended mechanism for posting actions back to the UI
    > thread is via a Handler
    > <http://developer.android.com/reference/android/os/Handler.html>: create
    > this from the UI thread, pass it to the background thread, and the latter
    > can use the post method to send back a Runnable to be executed.



    I don't like this idea. I'd bet that the UI thread already has a
    handler; it seems the intelligent thing to do. And I'd bet too that the
    handler for the UI is publicly available, in some publicly documented
    API. And I'll further suppose that they called the public method to
    access the UI handler "runOnUiThread".

    So don't do this, because you're just reinventing a perfectly good wheel.
     
    markspace, Apr 5, 2011
    #16
  17. On 05/04/2011 02:53, Steve Sobol wrote:
    > In article<>, Dirk Bruere at NeoPax
    > says...
    >
    >>>> Yes
    >>>
    >>>
    >>> Seriously? That's crazy.

    >>
    >> List of 1000 videos, in alphabetical order

    >
    > Listing them all, instead of allowing some sort of pagination, is a
    > horrible idea.
    >
    >

    Well, its a scrolling list.
    You can jump through it by tens or hundreds.
    1000 is also the expected practical max we will encounter. Most systems
    will have in the low hundreds

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 5, 2011
    #17
  18. On 05/04/2011 07:04, Steve Sobol wrote:
    > In article<>, Dirk Bruere at NeoPax
    > says...
    >
    >
    >> Well, its a scrolling list.
    >> You can jump through it by tens or hundreds.

    >
    > I should clarify my previous statement. It'll work if you're not using
    > the stock Android list control. I use K-9 Mail on my Android phone, and
    > it connects to my IMAP server which has dozens of folders (probably at
    > least 100) - and the list is manageable with 100-200 items. I would not
    > want to scroll through 1000+ items, it'd take forever.


    The min spec is a Tablet, Tegra 2, 512MB RAM, 1024x600, 10"

    >> 1000 is also the expected practical max we will encounter. Most systems
    >> will have in the low hundreds

    >
    > Well, that's different. "low hundreds" should be manageable.


    Anyone with more than a 1000 or so CDs, DVDs, video clips etc will have
    to wait for 2.0

    --
    Dirk

    http://www.neopax.com/technomage/ - My new book - Magick and Technology
     
    Dirk Bruere at NeoPax, Apr 5, 2011
    #18
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Dirk Bruere at NeoPax

    Java and Android

    Dirk Bruere at NeoPax, Aug 18, 2010, in forum: Java
    Replies:
    3
    Views:
    833
    Robert Kochem
    Aug 20, 2010
  2. appman
    Replies:
    0
    Views:
    486
    appman
    Apr 24, 2011
  3. Stef Mientki
    Replies:
    0
    Views:
    621
    Stef Mientki
    Nov 27, 2011
  4. P E Schoen
    Replies:
    3
    Views:
    1,033
    P E Schoen
    Sep 24, 2011
  5. sterta bole
    Replies:
    0
    Views:
    833
    sterta bole
    Aug 27, 2012
Loading...

Share This Page