Android Application Architecture (Android Dev Summit 2015)

hi there so as Dave said we're always a little bit more personable right after lunch we're also a little bit more excitable right after coffee so you're catching yeah just have coffee so this talk is about Android application architecture I'm Adam Powell I'm on the android framework team

I meet Boyer I'm also in the under a firmer team so this is actually going to be a little bit of a special extended edition version of a talk that we gave earlier this year at Google i/o just like every good special edition the original version isn't available

on video so here you are so the important thing about application architecture is really you need to act early in your applications as soon as you start writing code you've made these decisions that are going to affect how your application is going to be developed from there forward

so those decisions stick with you these really really deeply influence your application we're going to show you a few of the ways that this really affects how you might write your app as you move forward it really makes it easier or harder to think about the problems that

you're really setting to solve in your application so you might be asking okay well which patterns do I use there's a lot of stuff out there and you can really kind of get into a lot of alphabet soup as you start looking over all of the different prescriptive

patterns that you can blow into for I'm super confused yeah yeah I mean I've got all these things here that's me by doing customers so this isn't a survey of libraries that's not what we're here to talk about today and there really are a lot of great libraries

out there and you should really look into many of them and see what they can do for you and but I mean the trends change quickly the challenges you're going to face are really timeless the properties of some of these devices that we're writing for have been the

same properties from the first time any one of us held a smartphone and really lava flow happens over time this is kind of this phenomenon where after your code goes through a number of refactorings there isn't really kind of an overarching principle that ties it all together anymore

someone new who comes to your code base may or may not understand what's going on as you start piling up all of these different pieces that really no longer fit the important takeaway from this is to architect for user experience really nobody in your user base is going

to care exactly what patterns you used to produce your application they're going to care about the experience that they have while they've got it in their hand alright so let's let's look at some examples maybe some videos so here's an application it's very simple I'm posting your comment

on a photo trying to type right there you go send an hour waiting why am I waiting I have no idea I'm just waiting waiting all right it's sent so what was the problem here so what's happening was the user clicks on the send button it goes your

controller goes your network network comes out comes back whenever it wants to come back and then you update your UI meanwhile you block the user because you don't know what happened maybe the comment will not go what did we get rid of this introduced something you can call

a model which is your storage with where you keep your comments and everything so and then when the user hits the send button you keep it in your model updating you are they'll tell the network that this has been added and you can when the network on zurich

you can update you are so let's get the same example this time with the fix how it looks so I come here I taught my comment in the same comment so he makes use the same use case I send it I instantly see the comment is little bit

gray but I see it there when the network has finally comes back it turns black user has a clue you can design it better I couldn't but this idea you get you give the feedback instantly your designers are wonderful make sure that you appreciate them yeah I told

you why did you give me a design job ok let's look at one more demo ok is the same purpose I keep commented I'll like it I keep commenting and I hit Send ok you can see that it's like it's grayed out I see the response decently they

scrape it turns like best grade I go back to that page is empty like ours here literally two seconds ago I came back I came back to the page and it's not there like this is bad this is inconsistent I feel like something that lost so what was

happening here so the wheel the wheel controller as we said before told the model model told the network it came back we updated Ti great but the problem is the model they don't have anything right it only is still fed by the network what if instead you kept

a persistent model so is that your data stays on your client your there's something that you can always access unless the phone crashes which you cannot do anything in that case instead we have some application logic that's responsible to synchronize Network and you're persistent model is really important

to look at this problem as a synchronization problem rather than making API calls that simplifies a lot so this application logic seeks with the network fetches whatever you wanna fetch updates the model then just notifies anybody okay I'm done I made some changes hey guys if anyone's to

know about it here they are and then your view controller goes and fetches the data and updates the view let's say the user clicked on the send button the view control tells application logic I here okay I have this comment please send it the logic is the left

is the disk that's the first thing you have to do you update the local storage until hey like I updated this comment I added this coin and you also call the network now there's two things going on right now while you're making the network query the view controller

gets notified okay I have a new comment to fetch goes to model updates di when the network finally returns the application logical taste model again and says ok I seek to comment to slower if anybody's inserted like do something and then the view controller goes updates to live

again so every like everybody has like very simple duties and they are decoupled so how it looks let me implanted properly I come here is installed because it's coming from the local storage like it cannot be slow unless something is going really bad on my device which sometimes

happens alright sir well my example so fun ok it's running so I'm so excited that I keep sending comments but I'm only seeing the first comment I said like something is ok they just showed up like what happened there I don't know what happened there and I figure

out what happened there so this was my background case I had this great pull on the background that processes all these things one by one so that I don't create 2 main threats so when I loaded the activity to like a fetch with McJob came for the photo

and the other one to load the comments from the disk so I had to executives they started running meanwhile this user started spamming my application with all these new comments I finish it in the comments the other one came we put the comment in the disk and now

we are trying to send it to servers meanwhile the network is really bad so the fetching my job is to rank the wheel controller get a notification about the new comment I wanted to refresh itself but the cure is food like I'm using and then the job cannot

run so the way is not updating because I'm trying to fetch a bitmap like this is bad this is where the means the priorities are not good but it's very hard to know right you don't know how long the job will take you cannot estimate it all the

time but what you can do is separate these things so if I have a different queue for my network tasks and a different queue from my local tasks when my network is flaky my application is still responsive because they are never affected so if you run the same

jobs that we run the previous scenario as you can see now they are going into different threads and the local task queue just keeps running because disk is fine because we could have fished a bit but we couldn't update that we can send a comment to server but

who cares user is still seeing it and if we make that change as well so you just everything is sending and as they as it gets time to run the show jobs you're seeing that like they're starting to turn black from top one by one they will eventually

be synced okay so one of one of the feedbacks we get after lyre is like okay about how does the activity get notified so there is activity is a very very simple state machine in this scenario when it is created you set up your UI that's all you

do when you start you register for the unit's you want to know about and you load your data this is how it ensures that bit me restarts or whatever if there's some events you missed you will never miss them because they all start you register you refresh your

data and when an event comes just refresh your data it's like this hitting over all stop bound refresh your data you just are register from events so your life cycle is very very simple like this happens I do this this happens I do this this makes it easier

to test much more stable much more easier to understand okay back to you all right so the key takeaway from all the demo that we just saw there was really you need to design for offline I always assume that the network is not your friend it's going to

be slow and if you want to deliver a good user experience you really can't just blame the network for why something isn't popping in quickly the table stakes have really been raised on this when it comes to mobile so to solve this back your UI with a local

model and use your other application logic to sync the model on the server rather than doing this as live API calls it can be really tempting to try to optimize for the case where you want data freshness above all else where you really don't want to show anything

to the user unless you're really really sure about it but this is one of those things where if you assume success versus assuming failure then like what types of what types of claims are you really making about your software when you assume failure in the common case I

mean your software is going to work right your server is going to come back with the answer that you expected right so optimize for those case predict it show it to the user early by their I just learned yesterday a fancy new name called optimistic rendering so she'll

like know what is called optimistic rendering yeah I mean the actual thing that you see this principle at work in a lot of other areas of software development as well games is one really prominent example where a lot of network latency compensation systems in multiplayer games are based

on this principle they predict based on what happened in the previous known state from the network so you can use these same ideas in your own apps so make sure that these things don't necessarily depend on each other unless it's in a very loose fashion use event and

callbacks and such when needed to notify other parts of your system about state that's changed so everybody always asks us about dependency injection as well is it good is it bad what should I use what library should I use well I mean who use it if it helps

I mean everything has a costs so know the side effects of what it is that you're doing and what it is that you're including it's very very easy to include a new dependency in your application and it's not always easy to see what costs of that are going

to be at runtime especially when you're just bench testing at your desk everything is in the optimal case when you're sitting there at your desk so avoid reflection for better performance as well many dependency injection frameworks have a heavy runtime component versus a compile time component some systems

like dagger to do this at compile time and it ends up being much more optimal for your application so take a look into these and see what the trade-offs are when it comes to networking so we talked about other things that we can do always assume the network

is going to be slow janky always assume that you've got your phone in the middle of a conference with crowded Wi-Fi and completely slammed cell towers that's basically your case that you want to optimize for here because this is when people are going to notice that your application

is broken when it falls over so your API design at the network layer can actually affect this as well so design your back-end for your client not the other way around and this is one of those things that sounds really obvious when you get up here and say

it but as you start building server backends for these things you start exposing pieces that you may need and so forth and really start building it up as a series of layers so you have the the very low level components that provide your data such as the network

and then you have the higher level components that preside provide the presentation and it's very tempting to not have the higher levels inform the design of the lower levels but you can get a lot of wins by doing this also process as much as you can on the

server side to begin with you have these big beefy servers in the cloud that can do a lot of work for you don't offload stuff to the client that you don't have to more specifically pass metadata to the client this is something that can really really improve your

user experience if you have a large blob of data such as a big photo that you're going to pull down as part of syncing some sort of social app for example what do you know about this image at the time that you've made this request and gotten this

response back well not a whole lot so what are you going to show while that loads while you actually go fetch that image a better case would be to pass on along a little bit more metadata give your application some hints that it needs to succeed in this

case we're passing the width and height of this image in advance before we go fetch the image itself this allows us to perfectly sized a placeholder in our UI so that you don't have your UI jumping back and forth as the user Scrolls through or attempts to move

back and forth we've also passed along some pallet information here just to be able to give our UI a little bit more of a splash of color that keeps the flavor of the image before we actually have the real bit battery and data this is another concern that

you've got whenever you're talking to the network as well especially when you're doing any sort of background syncing that's your requests as much as possible now we've provided a lot of other tools in recent versions of Android to help you do this effectively things like doze and marshmallow

or great examples those will do a lot of this for you but really don't just rely on that as your sole way of dealing with this does is essentially the last line of defense for the user at this point use the job scheduler when it makes sense give

Android as much information about what it is that your app is actually trying to do and that way Android can make smarter decisions about how best optimize that the key takeaway here is act locally sync globally you really want to give the user a very fast very responsive

local experience whenever they're using your application and then make sure that you keep that in sync with the cloud almost as an afterthought compared with the presentation that you're trying to give so no talk of application architecture would be complete without some sort of addressing of the question

of activities and fragments this is something that we constantly get questions about even within Google we'll get questions with like oh okay well should I use activities or should I use fragments to build my apps like well that doesn't entirely make a whole lot of sense the question

fragments are really just encapsulated parts of an activity so when you build up your application when your activity starts getting too big and you need to start breaking it up so you can still think about it effectively it's a great time to break it down into some fragments

or if you start by building single fragments that sort of compose together a little bit more than that so you keep your concerns separated that can help keep things a little bit more straight in your head so that you don't end up with these giant activity God classes

fragments and views is another one that we get a lot now this is something that we talk about quite a bit on the framework team in terms of like one of the almost regrets of the fragment API at this point is that you've got this handy little tag

that you can just stick in your layout that says fragment and says go ahead and instantiate a fragment and stick it right here in my layout when I inflate it and this thing is really really convenient it makes for great code examples and demos because you can do

things like have a layout that sticks to fragments side by side and then you rotate the device and it sticks them stacked on top of one another and you say hey look look at this great decoupled system that I've got makes for a fantastic demo but realistically this

makes people think of fragments as being view constructs themselves and really they kind of aren't so they live in these very different worlds so views are really the nuts and bolts of your UIs that you're building whereas activities and fragments are the lifecycle constructs that basically provide the

plug-in points of contact with the rest of the system that tells you what's going on so many times you're going to want to use both to keep these responsive ladies clear just as you mentioned earlier you're going to want to use the signals that you get from your

activity and in conjunction with that through your fragments as well if you're using those to basically inform when you should register on register for events so on and so forth but the views should really kind of be their own encapsulated pieces separate from that that are controlled by

these other constructs okay so moving along to another topic of contention often memory so this is something that we talk about a lot if you've watched any of colt's talks on the Android performance practices you've seen some of these avoid allocating objects and hot code paths putting pressure

on the GC is not so great art is a lot better at this these days than Android used to be at all of this but there's still a lot of those devices out in the wild and if you watch this in a few sensitive code paths then you're

going to end up with a little bit of a smoother experience so you can pool and reuse some objects when you measure a problem that last part is really the key when you measure a problem make sure that you're measuring these things before you go and contort the

internals of your system and these are the sorts of things where some things you can make some decisions early that will help you move into these optimizations should you need them and some things it really doesn't matter so write the code the way that it's clearer first and

then optimize later so GC still kind of remains your enemy especially on some of these older devices even as the garbage collector in Android gets much better so perfect example of how this may affect your API design internally just with smaller components now what's the difference between these

two approaches well the top one is arguably more idiomatic much cleaner you can make some assumptions about it in terms of okay well the object it returns is probably hopefully safe if that's a mutable object to begin with but in order for that to be true then this

getter has to be allocating a new wrecked forming that it returns well now we've got allocation so where do you end up using that versus the second form here which is basically just taking an input parameter that the method is going to fill out you know that the

second one isn't going to perform any additional allocation in this case this is the sort of thing that you really only want to worry about in extremely hot code paths we're talking layout that gets run many many times we're talking drawing that gets run sixty times per second

if you're talking about things that happen in terms of your event handlers like clicks and so on and so forth you really don't need to be contorting your system this much internally but for that very small percentage of your code it is okay to write ugly code if

it helps your users your users are not going to see your code they're going to see your UI if your UI is ugly because it's behaving badly then that's something that they do see that's something that really does affect their experience the performance critical code really isn't the

majority and while all compiled code looks like we eventually anyways so with that we're going to move over to another demo that kind of brings a bunch of these ideas together okay yeah so after diode told one of the feedbacks we received was ok you taught these things

but show me some code that's it clear so this time we wrote a sample application keep in mind this a sample application and the main focus here is how do you look at this problem with a synchronization problem how do you design an application to work offline and

we are going to release the source code just waiting for some applause so here's my application I'll just lost my Chrome all right let's come here so so real app like there is a real server running on my computer it's for the purpose of the demo and the

application is just a list of posts oh hello everybody I can say I could say everybody okay so how does this work so it's this do something dangerous and just it's not that dangerous all the more danger like I said I just disabled to network and I send

one more so right now like please forgive my UI but so there is there's a upload Club on the right bottom of text that tells the user this is not the same as otherwise is almost going to send it's not there yet so if I actually look at

my rails server and my application when the network recovers it's all eventually post it is going to update di now some other examples we saw in the previous time actually we can crash the server too so how the server is not running anymore and so for this demo

I have a like very simple back off time but it just it will just cry try try back off and I got my application yeah it was created these are my application logs here is actually keep trying to post it because there is Network just a service crashing

and now when I finally bring the server up oops not like that not like that okay so I mean it started backing off for the it will eventually send a comment to the server dies that it keeps me trying and yes it did send so it's go one

more case where we do crash the server again ok one more comment is the same oh wait it didn't crash oh sorry I crash the logger good alright so we crash the server this time see if we can she works hello i'm wrote a server that doesn't even

crash right come on we should go alright ID should crash for the purpose of to them all i hope your service won't crash so I said one more comment and then I simply go out so my emulator make crash though it's been having some problems ok I did

go out and I am going to kill that application I come back oops ok so the comment is still there it cannot update defeat but the comment is waiting for me there and I will just run the server screen team actually synchronize this idea the application doesn't care

about when the comment because sting in the disk waiting to be uploaded there's some other job that's taking care of the upload but it will eventually go to server unless I have a bank of course so how do we how do we do take care of these things

the way it works is we have this feed activity but by the way again like this is a demo application track to focus on certain things now might be other problems with this demo application the important takeaway is the thing we are focusing here so this application when

you call Sun post it simply tells the feed controller to send the post which adds a job to the displaced like something purchased and they will eventually run which is responsible to update the disk as well as sending the post so it updates the disk and then dispatches

an event when this is event is this page my activity knows about this event refreshes itself when it runs does the same thing or is canceled it does the same thing this is a persistent job that means is going to be saved in the disk until exceeds or

reaches the retry limit this is OK alright the comment is posted nice and so another problem about synchronization what happens if I'm offline send comments so it's not working eventually and I go to this website and I create a point here I am from the web all right

so now these two states are inconsistent right my client doesn't know about the new one I have it and I have a comment locally so if I enable my network by the way normally when the network comes back you would refresh your feet but I'm not doing it

for the purpose of the demo so when the network comes back is going to send the post instantly and so is all those thing but when you actually refresh its going to add anyone to the correct order this is because everything is time-stamped by the server so until

a post is sent to the server you make a best guess timestamp on it so for this example the way I do it is again it may change per your application but the way this one does is let me open the save feature okay so when it creates

it actually queries the model to get the best timestamp I have this is a way to synchronize if my client clients time is like really really off and if the NIP comment is not on top we have a look really bad because I change your server ordering by

the UI ordering but that may cause other problems so what I do here is like I I assign a best case timestamp locally and then we do it on later and servers so we hit it off the network again and again like another case because of the persistence

so I send a post here click on a user names moves you to the users post and it's not like I never faced this user user feet but the process that is everywhere in my application because on my disk I don't care if it is not synced or

not I have a proper model for that objects a simple value object but it's always working and let's say I'm in the user feet but then recover the network it will eventually set and the field will update to if I go back is already updated here instantly because

like if a UI is interesting a certain event it listens for it now you could probably implement a global event bus here you can probably implant the same thing with rx or broadcast like this is a sample application it does this way and it works there might be

multiple ways to do the same thing the writing might be a better way so we'll get more fancy okay like what if the server is crashing so this is my real server by the way sort of fun the magical language so I'm going to turn on a flag

here which is going what this will do is the server is going to save the post but it's going to crash afterwards so I won't be able to realize that I could synchronize that post so the service question now I go back here I can say okay how

I wish so the job tries 20 times and it breaks off 250 milliseconds exponentially so it will keep trying trying trying try me again all right so now the server is crashing as you can see the crash shocks it saves the post to the disk but the client

doesn't know so client keeps the track but what happens that your fetch feeds post endpoint is working fine so i refresh so the post came back I know it's the same pause so how do I know it's the same post this comes in to a little bit API

design but I we wanted the full demo so I I did figure out the same post and updated although my son positive and never succeeded so the way it works in this application is by design each each element is assigned a client ID so if you look at

these examples here visible yeah alright so there's a user ID and there's also client ID this client ID is randomly generated and unique per user per post so there's a unique in Midas's user ID and client ID is a unique topple you rely on the fact that it's

not going to conflict but yeah I mean technically it can probably it want so by doing this when I receive the post from the server I know that posterity exists in my disk and I update that fun saying that okay I sing this one I don't know what

happened to the job I don't care and then the job knows while running when it before trying to send the post it checks a is this already synced so here like if you can see the code here just loads it it's already synchronized doesn't do any to go

like a this has been updated believes everything's working fine so way one more thing okay so the muscle idea here is that like you do this works because we look at the problem as two separate problems one of the reason responsible take the user interaction process the model

locally and the other one is responsible to synchronize with the server however it does it's not trivial you are writing a bunch of additional code to say it is like local client IDs you I'd handle conflicts but it works so let's get one more demo here this time

I will shut down the server okay so I'm standing I'm again that excited kit I keep sending all these messages of course they are not going to go if I look at here they are there they're not going when I recover the server now the first job is

backing off it will line to the finish meanwhile I can send mark as you can see they are going up one by one one by one in the correct order my scroll position knows this works because these jobs are running through the same queue purple so I know

that whoever came first will they handle first so we guarantee that they're in orders we cannot guarantee their thumb stands because this technically not possible unless you want to rely on what your client timestamp is but we rely on we granted their orders again this is all independent

of the I it doesn't care at all the only thing it knows is how do I load this from the disk and how do I send this comes to my application logic one more example I want to go actually I want to go through this collet so one

of the problems with this event there in architecture is since all this is happening although I synchronize I qualify missing you and or what if the timelines x times don't match again for this demo application the way this one works is every single event that is important comes

with a timestamp so for example for fetching the freedom and timestamp is the timestamp of the oldest post so that if someone is going to load those ones it makes you it includes that timestamp in the query so it lost things afterwards that so every time the feed

activity receives an event let's say on you entered it checks if this for my user if this for my user just cause the refresh rate to all this one and then add refresh method while creating the model it uses that timestamp or if there is no time study

it uses the top one because you don't want to keep your fetching things yeah that is mostly let me see if I miss anything yeah okay I think so we are going to publish this code both the server side and the client side to github as soon as

I need some clarification here and there but and you can play with it we all be happy to get polar casts or whatever if you think something could be done better and this is nice to share and like this move on from there right sure that's it is

time for some questions I think that we've got a microphone floating around perhaps sir there we go we'll really contain tests how will will the project contain contain tests yes yes actually does so I actually I should have showed it so if you look at the hobby well

they may not be great tests say I'm not a testing expert but they do this mostly their most integration test if you look at something more fancy for example fetching the feet so I say we do the slide back over to the computer robot oh sorry can we

go back to the computer okay thank you so the application uses dagger this is how I mock some of the API calls but they see H feet enough sand sand command that will be nice so short answer yes yeah yeah they're here that's a very nice test I

can find okay five hundred four hundred so it's like I mean this is creating the job mocking the API so so that if someone tries to send a post return 404 by injects the test component runs the job checks the appropriate descent so all of one of the

things is like is not even when they some outputs happen as you ants it's not very easy to test them in this example I'm using in the test application I'm using your logging event bus that basically logs every single event so that I can assert on those later

on yeah like there might be different or better ways to do this but this is something that works on we have tests so hi um what's your opinion on using custom views instead of fragments so you don't have to deal with the fragment quirks if you will so

things like child fragment manager or popping the back stack when after us a save state so again I think that those are they're kind of unrelated just because they I mean a fragment controls views it's not a view itself I mean that was kind of what we were

trying to get out a little bit earlier overall I think that a lot of people write fragments when they really meant to write a custom view so I think that in in many cases see exactly what it is that your particular chunk of encapsulated UI needs to express

and respond to if those are events that can be coming from some alternate component that's kind of composing that pieces place within the overall UI then that speaks to it being probably a little bit more like a view group if it's something that really needs to respond to

other elements of the application lifecycle on the activity lifecycle if it's doing something like registering for events dealing with things like that then it may be more appropriate for a fragment in that case but overall I think that people tend to lean a little bit too heavily on

fragments when they think of one particular sub segment of their UI hi um one question I had was how do you handle the serialization of these jobs to disk and back out when the app dies do you roll your own logic or do you use something like a

framework or something uh so in for this demo application I'm using Java key which is some library I wrote before you can simply serialize them you can use tape like that was recently another talk to use tape to serialize your stuff to make it like a job there's

a multiple solutions for this out there is not that complex either you can choose one of them which fits your needs cool thanks we've got one up front in this kind of application would you use a sync adapter or would you use like ASIC rest calls to connect

sir so for the demo so one of the things we gave examples in the slides was to separate your local tasks from the server tasks the way we do it is for your eye related stuff I'm using guessing tasks I never use it for network only for you

I stop and for network I'm using the job queue so the jobs don't master : to network yeah the sync adapter is generally useful if once you have a more sophisticated account infrastructure and you have some of the other machinery that that was really kind of built around

so if it seems like you're going through a lot of additional machinery to work with a sync adapter then maybe start taking a look at some more things just like a job scheduler so on and so forth and see if that's something that is simpler that can still

meet your needs when you start building all these applications with these different versions of data models so you might have like fetch data off the network updates that the user has made to the data that hasn't been actually pushed to the database do you guys have any classes

it can help us maintain all this application logic or any libraries that you would suggest we use to maintain all these four versions giving any more suggestions or not I am sure not nothing about versioning but they actually remind me something I forgot in this one of the

important things in your client is your clients consistency and the soulmate crash which is so may also start returning bad data so one of the things this Clyde does is if I open a user model let's say everything everything save so before we save anything we well date

it and if it is not valid for this client we just throw it out because I don't understand maybe the server has a bug maybe so is any version I don't know we just if I don't understand object I throw it out this makes it easier for the

rest of the application to like a gift object is that is properly filled so to the point of dealing with the specific data inconsistency is especially across upgrades different server versions on and so forth what we found when working with a lot of teams both within Google and

external to Google is that many times the the actual pieces of business logic that make an app interesting tend to differ so much between applications that we've had a lot of difficulty finding a one-size-fits-all solution to just offer as like a prescription of hey everyone go use this

for dealing with this particular problem space it is something that you can make a lot of assumptions based on the shape of your data and what it is that your data is actually representing so oftentimes you need something a lot less complex than some general solution that solves

for the entire world but if you've got some ideas please track some of us down in the halls here and we'd love to hear about them anyone else or okay all right well thank you very much thank you

Hãy bình luận đầu tiên

Để lại một phản hồi

Thư điện tử của bạn sẽ không được hiện thị công khai.


*