← Saturday, December 3 | Yesterday, December 7 →
Dec 6 | 3:50 PM |
Mark M. | has entered the room |
Mark M. | turned on guest access |
Dec 6 | 3:55 PM |
Steve S. | has entered the room |
Steve S. |
Hi Mark!
|
Mark M. |
hello, Steve!
|
Mark M. |
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.
|
Steve S. |
I can start by showing you my database helper code:
|
Steve S. |
View paste
(20 more lines)
|
Steve S. |
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
|
Mark M. |
and, long-term, you'll need a more sophisticated onUpgrade(), most likely, though you probably already know that
|
Mark M. |
otherwise, I don't see any problems
|
Steve S. |
ok
|
Steve S. |
Per your advice, I've tried to create a cache for the list of contacts stored in the database:
|
Dec 6 | 4:00 PM |
Steve S. |
View paste
|
Steve S. |
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
|
Mark M. |
(BTW, hello, Chris -- I will be with you shortly)
|
Mark M. |
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
|
Steve S. |
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
|
Steve S. |
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?
|
Dec 6 | 4:05 PM |
Chris G. |
Yes. I typed it up ahead of time. Noob question :D
|
Chris G. |
I'll see how it paste in here...
|
Chris G. |
View paste
|
Chris G. |
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
|
Mark M. |
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
|
Mark M. |
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
|
Mark M. |
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.
|
Dec 6 | 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
|
Mark M. |
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.
|
Chris G. |
ok yeah that was going to be my follow up question.
|
Chris G. |
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
|
Mark M. |
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:
|
Steve S. |
View paste
(3 more lines)
|
Mark M. |
the joys of pseudo-code is that it always compiles :-)
|
Steve S. |
indeed
|
Steve S. |
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
|
Dec 6 | 4:15 PM |
Steve S. |
regarding the contact list kept in memory: what should i think terms of regarding a reasonable maximum hap size?
|
Steve S. |
*heap size
|
Mark M. |
you can use ActivityManager and getMemoryClass() to find out the heap limit for your app
|
Mark M. |
that will return the approximate number of MB of space (e.g., 32)
|
Steve S. |
ok
|
Steve S. |
but a reasonable maximum heap size would be device-dependent?
|
Mark M. |
yes, which is why you use getMemoryClass() to find out your limit
|
Mark M. |
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
|
Mark M. |
er, space
|
Steve S. |
i think the contacts cache is pretty much it
|
Mark M. |
today, perhaps
|
Mark M. |
who knows what tomorrow may bring
|
Steve S. |
right
|
Steve S. |
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?
|
Mark M. |
so, for example, suppose you are willing to give 10% of your heap limit to the contacts caches
|
Mark M. |
getMemoryClass() * 0.1MB gives you your cache size
|
Mark M. |
(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
|
Mark M. |
LRUCache has hooks for that
|
Dec 6 | 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.
|
Chris G. |
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
|
Mark M. |
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...
|
Chris G. |
right now i mean
|
Mark M. |
an IntentService probably isn't the right answer
|
Mark M. |
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
|
Mark M. |
because the default use of Handler gives you control on the main application thread
|
Dec 6 | 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.
|
Chris G. |
just rest periods.
|
Mark M. |
oh, OK
|
Mark M. |
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
|
Chris G. |
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.
|
Chris G. |
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.
|
Chris G. |
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:
|
Steve S. |
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
|
Dec 6 | 4:30 PM |
Steve S. |
i see
|
Mark M. |
but, now we have multi-window
|
Mark M. |
the difference is...
|
Mark M. |
...onPause() is called when you no longer are capable of receiving input, but are still visible
|
Mark M. |
...onStop() is called when you are no longer visible
|
Mark M. |
in a single-window environment, most of the time, those two events happen within microseconds of each other
|
Mark M. |
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()
|
Mark M. |
now, onStop() is as reliable as onPause()
|
Mark M. |
and, with multi-window, you will be paused (and not stopped) a lot more of the time
|
Mark M. |
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
|
Mark M. |
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
|
Steve S. |
Another question: now that there is RecyclerView, should I always use that instead of (even simple) ListViews?
|
Mark M. |
"always" is a strong term
|
Mark M. |
for new construction, I would lean towards RecyclerView, as it is more flexible and will get more love going forward
|
Mark M. |
this does not mean that ListView is evil and must be destroyed
|
Steve S. |
ok
|
Dec 6 | 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
|
Steve S. |
No more questions today. This has been extremely helpful, as always. Thank you so much!
|
Steve S. |
Have a good rest of the day!
|
Mark M. |
you too!
|
Steve S. | has left the room |
Dec 6 | 5:00 PM |
Mark M. | turned off guest access |
Dec 6 | 5:20 PM |
Mark M. | has left the room |