Office Hours — Tuesday, December 6

Saturday, December 3 | Yesterday, December 7

Dec 6
3:50 PM
Mark M.
has entered the room
Mark M.
turned on guest access
3:55 PM
Steve S.
has entered the room
Steve S.
Hi Mark!
Mark M.
hello, Steve!
how can I help you today?
Steve S.
We had a chat a few weeks ago that convinced me I needed to re-architect my app to replace modal progress dialogs (shown while waiting for Rest requests to complete) with modeless ones. I'm interested in getting your feedback on what I'm thinking of for the new architecture.
I can start by showing you my database helper code:
View paste (20 more lines)
public class DatabaseManager {
    static private DatabaseHelper sDbHelper = null;

    static private synchronized DatabaseHelper getInstance(Context c) {
        if (sDbHelper == null)
            sDbHelper = new DatabaseHelper(c.getApplicationContext());
        return sDbHelper;
    }

    static SQLiteDatabase getReadableDb(Context c) {
        DatabaseHelper h = getInstance(c);
        return h.getReadableDatabase();
    }

    static SQLiteDatabase getWritableDb(Context c) {
...
I'm wondering if this looks ok to you.
Mark M.
if DatabaseManager is just a bunch of statics, I'm not sure why you don't fold DatabaseManager and DatabaseHelper into a single class
and, long-term, you'll need a more sophisticated onUpgrade(), most likely, though you probably already know that
otherwise, I don't see any problems
Steve S.
ok
Per your advice, I've tried to create a cache for the list of contacts stored in the database:
4:00 PM
Steve S.
View paste
public class ContactsCache {
    static private List<Contact> sContacts == null;

    static public List<Contact> getContacts(Context c) {
        if (sContacts == null)
            sContacts = ContactDao.getContacts(c);
        return sContacts;
    }

    static public void updateContacts(Context c) {
        sContacts = ContactDao.getContacts(c);
    }
}
The next thing will be to show you how the cache is used - that might bear on whether the cache looks ok.
Mark M.
how many contacts might there be, and how large is a contact in terms of heap usage?
Steve S.
good question, and something I haven't thought about - but it sounds like i should.
Mark M.
the problem with List<Contact> is that, by default, it only ever grows
Chris G.
has entered the room
Steve S.
ok
Mark M.
and so you have an intentional memory leak
(BTW, hello, Chris -- I will be with you shortly)
you might consider using LRUCache, if you are in position to deal with cache misses, loading the data on the fly when needed
Steve S.
wouldn't it get smaller if contacts are deleted?
Mark M.
yes, but that's a user thing
Steve S.
ok
Mark M.
users don't want to have to delete contacts just because you run out of heap space
Steve S.
right
ok. i'll look into LRUCache.
Mark M.
now, if you're sure that the number of contacts will be small, and the size of an individual contact will be small, List<Contact> may be fine
Steve S.
there is now a limit of 25 contacts
though i'll need to think about how big each contact is
Mark M.
let me take a question from Chris, and I'll return to you in a bit to continue this discussion
Steve S.
sure
Mark M.
Chris: your turn! do you have a question?
4:05 PM
Chris G.
Yes. I typed it up ahead of time. Noob question :D
I'll see how it paste in here...
View paste
I'm creating a very simple app for use at the gym that will allow me to select an alarm from a list view and then display the progress as a notification and then plays an alarm sound when the time is up. I want to be able to pull the phone out of my pocket and see the notification on the lock screen showing me how much time I have remaining. I used it at the gym and everything worked great. However, I almost always have music playing at the gym so I wondered what would happen if I didn't have any music playing and tried to use my app. Sure enough the device goes to sleep and my notification stops updating. I looked at various methods of making sure the notification keeps updating even when the phone goes to sleep but none of them worked. I did get it to work using a wakelock and having my app sleep with Thread.sleep(), but that doesn't seem to be an ideal solution. 

I was looking at AlarmService but that doesn't seem to be the right tool and I saw in your book that alarms can't be scheduled for less than 5 seconds out.

The notification doesn't need to be ultra precise, but I'd rather not lose seconds here and there depending on how I'm using the phone.
This app is just for me and I'm not too concerned about battery life.
Mark M.
there are probably better solutions for the timing than sleep(), such as ScheduledExecutorService
however, a WakeLock is your only real answer, if you want continuous updates while the screen is off
Chris G.
OK, I was going to look into that one as well. I thought I saw that it didn't work when the device went to sleep but not sure.
Mark M.
and bear in mind that you will need to add your app to the battery optimization whitelist in Settings, if it is running Android 6.0 or higher
Chris G.
okay. so do you think this is a valid use case for wakelocks?
Mark M.
for an app for your own personal use? sure
if you don't like your app's battery consumption, you can look in a mirror and yell at the developer :-)
Chris G.
haha yeah
Mark M.
if you were going to ship this on the Play Store or other channels, you might want to give the user a choice of update style
Chris G.
ill look at the ScheduledExecutorService too. i have a follow up but you can return to Steve.
Mark M.
one approach gives continuous time updates but chews up the battery
the other approach is more coarse-grained on the updates, but is more battery-friendly (using AlarmManager and a one-minute update interval)
Chris G.
ok. there is another workout app that does what im trying to do. if it's using wakelocks, im used to the battery consumption. it seems fine to me.
4:10 PM
Mark M.
particularly if you are already playing music or otherwise keeping the device awake, the incremental power drain of your app will be negligible
what you don't want is to be the sole reason the CPU is powered on and for the user not to understand why you are keeping the CPU powered on
Chris G.
my rest intervals are usually :30 to 3:00 minutes and wanted to start my next set as close as possible to the set time.
ok yeah that was going to be my follow up question.
ok thanks for the help.
Mark M.
let me switch back to Steve, and I'll come back to you in a bit if you have further questions
Steve: back to you!
Steve S.
ok, let me show you how the ContactsCache and other things are used. The goal is to add the new contact to the listview with a progress bar, wait for the rest request to complete, and then update the listview with full contact information:
View paste (3 more lines)
public class RoamFreeService extends IntentService {
    protected void onHandleIntent(Intent i) {
        get contact name and number from intent
        ContactDao.addContact(this, name);
        ContactsCache.updateContacts(this);

        //display list of contacts with a progress bar next to new contact name;
	// UI fragment will call ContactsCache.getContacts() and display in ListView:
        post contact list refresh event to UI fragment

        // obtain info about new contact received via Rest request
        Contact c = ApiClient.addContact(name, number);
        ContactDao.addContact(this, c);
        ContactsCache.updateContacts(this);

...
Mark M.
the joys of pseudo-code is that it always compiles :-)
Steve S.
indeed
but maybe there are things it's obscuring...
Mark M.
I can't really comment on the code listing itself, though nothing strikes me as being a problem
Steve S.
ok
4:15 PM
Steve S.
regarding the contact list kept in memory: what should i think terms of regarding a reasonable maximum hap size?
*heap size
Mark M.
you can use ActivityManager and getMemoryClass() to find out the heap limit for your app
that will return the approximate number of MB of space (e.g., 32)
Steve S.
ok
but a reasonable maximum heap size would be device-dependent?
Mark M.
yes, which is why you use getMemoryClass() to find out your limit
you will need to decide how much of that you want to allocate to the contacts cache, versus other caches (e.g., image cache for contact photos), plus working sapce
er, space
Steve S.
i think the contacts cache is pretty much it
Mark M.
today, perhaps
who knows what tomorrow may bring
Steve S.
right
is there a maximum heap size that would be reasonable on the majority of devices in use?
Mark M.
um, why not call getMemoryClass()?
Steve S.
ok
Mark M.
IOW, what is the advantage of guessing, when the device will tell you your heap limit?
so, for example, suppose you are willing to give 10% of your heap limit to the contacts caches
getMemoryClass() * 0.1MB gives you your cache size
(more completely: getMemoryClass() * 1MB * 10%)
Steve S.
would this be used then for a more intelligent cache, that limits the number of contacts in the cache based on what getMemoryClass() returns?
Mark M.
correct
LRUCache has hooks for that
4:20 PM
Steve S.
i see. then i'll look into LRUCache.
Mark M.
let me switch back to Chris, and I'll return to you shortly
Steve S.
sure
Mark M.
Chris: back to you! do you have another question?
Chris G.
Yep.
I mentioned i was able to get my app working the way I want using a wakelock. I did this by doing a lock then Thread.sleep(1000) and then an unlock. I don't have the code in front of me.
Mark M.
that's probably not what you want
Chris G.
but previously i was using a handler and postDelayed(1000). im not sure how to use a wakelock with a handler. i tried putting the wakelock's lock/unlock all over and nothing seemed to work.
Mark M.
you would want to acquire the WakeLock before the entire timing interval (e.g., 30 minutes), and release it when that is done or the user cancel the timing
Chris G.
oh ok.
Mark M.
your timing logic needs to be in a Service, as otherwise Android might terminate your process before your workout is over
Chris G.
what about with a handler and using postDelayed?
Mark M.
I don't see the valeu
er, value
Chris G.
ok. right not it's in an intent service but i also have a bound service that does the same thing. been trying a lot of things out...
right now i mean
Mark M.
an IntentService probably isn't the right answer
a regular service (bound or otherwise) would be better
Chris G.
yeah i was kind of testing/learning and moved everything back to a bound service and then that's when i ran into this sleep issue.
Mark M.
in terms of the Handler, postDelayed() is cool for loose in-process timing, when you need to update a foreground UI
because the default use of Handler gives you control on the main application thread
4:25 PM
Mark M.
and you need that to be able to update the UI
Chris G.
but im not using the wakelock for the entire workout. just for rest between sets. like 30, 90, etc seconds.
Mark M.
are you timing the whole workout, or just rest periods?
Chris G.
i was thinking about updating the main ui, but right now im only updating the notification.
just rest periods.
Mark M.
oh, OK
is this like a HIIT timer?
Chris G.
say i do a rest, pull out my phone and see the notification and click reset to start again. then the alarm goes off in 90 seconds and repeat
not really but i imagine it can be used in the same way
Mark M.
just trying to draw comparisons to similar timers that I have used, that's all
Chris G.
the amount of rest has a big impact on how i perform so i want to do some works at a consistent say 1:30 min rest periods.
Mark M.
if you're only timing the rest periods, then you only need the WakeLock during the rest periods
Chris G.
correct, that's what im doing now.
it seems to work so far but i wanted to do a handler.postDelayed instead of Thread.sleep. unless you think it doesn't matter.
i just want to dot he right thing even if it's just for me.
Mark M.
actually, of those too, I'd use sleep()
Chris G.
haha ok well that makes it easy.
Mark M.
a Handler, by default, will give you control on the main application thread, which you don't need
Chris G.
ok thanks for the help. i need to head to lunch. i appreciate it.
Mark M.
my ScheduledExecutorService option is still a candidate, though it's less of a fit than I was thinking
Chris G.
ok ill look into it again
Mark M.
tell lunch I said "hi!" :-)
Chris G.
has left the room
Mark M.
Steve: back to you!
Steve S.
thanks. here's another questions:
For LocalBroadcaseManager, I have been registering it on onResume() and unregistering in onPause(). It looks like you do the same for the Greenrobot EventBus, which I am now looking into. However, in their documentation they register/unregister in onStart()/onStop() respectively. Which should I prefer? Does it matter?
Mark M.
actually, I am slowly cutting over to onStart()/onStop()
Steve S.
ok. how come?
Mark M.
for most situations, prior to Android 7.0, it would not really matter
4:30 PM
Steve S.
i see
Mark M.
but, now we have multi-window
the difference is...
...onPause() is called when you no longer are capable of receiving input, but are still visible
...onStop() is called when you are no longer visible
in a single-window environment, most of the time, those two events happen within microseconds of each other
and, way back in the Android 1.x/2.x days, we were not given a guarantee that onStop() would be called, so we leaned towards onPause()
now, onStop() is as reliable as onPause()
and, with multi-window, you will be paused (and not stopped) a lot more of the time
so, you need to decide what the right thing is for the user, if the user has you in one window, then taps on and starts working in another window
if the right thing is to keep updating your UI, you would use onStart()/onStop() for the event bus registration
Steve S.
when you say "no longer are capable of receiving input", that's input from the user?
Mark M.
correct: screen taps, key events, etc.
Steve S.
ok
Another question: now that there is RecyclerView, should I always use that instead of (even simple) ListViews?
Mark M.
"always" is a strong term
for new construction, I would lean towards RecyclerView, as it is more flexible and will get more love going forward
this does not mean that ListView is evil and must be destroyed
Steve S.
ok
4:35 PM
Mark M.
in particular, unless you are looking to solve some specific problem, I would not replace existing code using ListView with RecyclerView "just because"
Steve S.
ok
No more questions today. This has been extremely helpful, as always. Thank you so much!
Have a good rest of the day!
Mark M.
you too!
Steve S.
has left the room
5:00 PM
Mark M.
turned off guest access
5:20 PM
Mark M.
has left the room

Saturday, December 3 | Yesterday, December 7

 

Office Hours

People in this transcript

  • Chris Greene
  • Mark Murphy
  • Steve S