****** review 1 ******
title: Ahhhhhh
content: Ahhhhhh
author: Andrew288195Az
rating: 1
****** review 2 ******
title: Perfect except
content: I still have to switch to safari to "Ctrl+F" a web page.
Seriously?
author: SarahTimmins
rating: 1
****** review 3 ******
title: Crashes too much
content: Title pretty much says it all.
author: RoboWarriorSr
rating: 2
****** review 4 ******
title: My penis is big
content: I have a penis of 1 feet
author: Jiackfabri
rating: 5
...
****** review 49 ******
title: Works great!
content: I was skeptical but I actually really like this browser. Good job Googlé.
author: eSquish
rating: 5
****** review 50 ******
title: Amazing
content: This app/browser is fantastic! It runs fast, and efficient it has shown me the awesomeness that is google and convinced me to buy a chromebook
author: Seandog247
rating: 5
We can only get 50 reviews, but the feeds updated more frequently than the ranking feeds.
There are four types of reviews list,
12345678
Most Recent
https://itunes.apple.com/us/rss/customerreviews/id=535886823/sortBy=mostRecent/xml
Most Helpful
https://itunes.apple.com/us/rss/customerreviews/id=535886823/sortBy=mostHelpful/xml
Most Favorable
Most Critical
I couldn’t figure out what the feeds URL are of last two types, if anyone knows, please leave a comment, and I’ll update this post.
I was working on a mobile app analytics system recent months, how well the system works relies on the app usage data captured on the users’ devices.
This post is about technically how I did it in both iOS and Android operating systems.
Making Thread
When a user opens an app, an open session event needs to be captured, when a user tap the home button, app transitions to the background, the session needs to be marked as closed, when a user view an important page, or click a fancy button, counters need to be increased. Those needs must be handled in background threads since I don’t want my app UI freezing by some time-consuming “needs”.
In iOS, Grand Central Dispatch (GCD) is amazing, create a serial dispatch queue, and a background thread is alive.
A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue.
In the first place, design a handler class SessionHandler inherits Handler.
123456789101112131415161718192021
publicclassSessionHandlerextendsHandler{publicstaticfinalintMESSAGE_OPEN=0;publicstaticfinalintMESSAGE_CLOSE=1;publicSessionHandler(finalContextcontext,Looperlooper){super(looper);}publicvoidhandleMessage(finalMessagemsg){super.handleMessage(msg);switch(msg.what){caseMESSAGE_OPEN:this.open();break;caseMESSAGE_CLOSE:this.close();break;default:Log.i(SessionHandler.class.getName(),"Can't handle this message");}}
If these tracking methods are provided as an SDK, app developers may not want to add those tracking methods one by one in their code base. So, the interfaces of this kind of SDK should be minimum. There are things can be done automatically.
As in iOS, we could bind method calls to system events, for example, when app transitions to background or foreground.
I added below code to main method in SDK, app developers only need to call this main method in their code base, believe me, they will love your SDK.
In Android, sadly, I couldn’t find anything similar. It does provide system event bindings in Application, but only onCreate and onTerminate, useless for my needs. So I gave up, and looked for alternatives in Activity, maybe Application.ActivityLifecycleCallbacks could be helpful, but API level (14) is too high for me.
Sending Data
Process
The data captured are on the users’ devices, they should be transferred to server for analysis.
Although the data is anonymous, it’s about users’ behavior, encryption is needed to keep it safe during transfer.
The target devices often connect to cellular network, it’s much expensive than WiFi, compression is needed to keep the data size as small as possible.
For encryption, I implemented two ways, AES and RSA, RSA is more secure, but AES is easy to implement and use. Below are AES implementation in iOS and Android.
-(NSData*)gzipDeflateData:(NSData*)data{if([datalength]==0)returndata;z_streamstrm;strm.zalloc=Z_NULL;strm.zfree=Z_NULL;strm.opaque=Z_NULL;strm.total_out=0;strm.next_in=(Bytef*)[databytes];strm.avail_in=[datalength];if(deflateInit2(&strm,Z_DEFAULT_COMPRESSION,Z_DEFLATED,(15+16),8,Z_DEFAULT_STRATEGY)!=Z_OK)returnnil;NSMutableData*compressed=[NSMutableDatadataWithLength:16384];// 16K chunks for expansiondo{if(strm.total_out>=[compressedlength])[compressedincreaseLengthBy:16384];strm.next_out=[compressedmutableBytes]+strm.total_out;strm.avail_out=[compressedlength]-strm.total_out;deflate(&strm,Z_FINISH);}while(strm.avail_out==0);deflateEnd(&strm);[compressedsetLength:strm.total_out];return[NSDatadataWithData:compressed];}
In iOS, I wrapped sendSynchronousRequest of NSURLConnection to a dispatch queue.
123456789101112131415161718192021222324
NSMutableURLRequest*request=[NSMutableURLRequestrequestWithURL:serverUrlcachePolicy:NSURLRequestReloadIgnoringCacheDatatimeoutInterval:60.0];[requestsetHTTPMethod:@"POST"];[requestsetValue:@"application/x-gzip"forHTTPHeaderField:@"Content-Type"];[requestsetValue:@"gzip"forHTTPHeaderField:@"Content-Encoding"];[requestsetValue:[NSStringstringWithFormat:@"%d",[jsonDatalength]]forHTTPHeaderField:@"Content-Length"];[requestsetHTTPBody:dataEncrypted];dispatch_group_async(self.dispatchGroup,self.anotherDispatchQueue,^{NSURLResponse*response=nil;NSError*responseError=nil;NSData*responseData=[NSURLConnectionsendSynchronousRequest:requestreturningResponse:&responseerror:&responseError];NSIntegerresponseStatusCode=[(NSHTTPURLResponse*)responsestatusCode];if(responseError){// handle failure}else{if(responseStatusCode==200){// do things}else{// handler failure}}});
Why I use dispatch group is because I don’t want the uploading task block the tracking tasks, so the task is dispatched to another queue, in many circumstances, we have to wait all the tasks in tracking thread and uploading thread are finished, then do somethings.
For example, if I want to send all the data captured every time app transitions to background, five seconds is not enough sometimes, app will crash and it’s a bad user experience. We can add a long-running task by using beginBackgroundTaskWithExpirationHandler, when all the tasks in this dispatch group are finished, notify the system our long-running task is finished.
123456789101112131415161718192021222324252627
[centeraddObserverForName:UIApplicationDidEnterBackgroundNotificationobject:nilqueue:[NSOperationQueuemainQueue]usingBlock:^(NSNotification*notification){UIApplication*application=[UIApplicationsharedApplication];__blockUIBackgroundTaskIdentifiertaskId=[applicationbeginBackgroundTaskWithExpirationHandler:^{Dispatch_async(dispatch_get_main_queue(),^{// If task is overtime, end itif(taskId!=UIBackgroundTaskInvalid){// Failed to finish background task, cleanup[applicationendBackgroundTask:taskId];taskId=UIBackgroundTaskInvalid;}});}];[selfclose];[selfupload];dispatch_group_notify(self.dispatchGroup,dispatch_get_main_queue(),^{// Finished executing background taskif(taskId!=UIBackgroundTaskInvalid){[applicationendBackgroundTask:taskId];taskId=UIBackgroundTaskInvalid;}});}];
In Android, as I did before, create another handler UploadHandler in SessionHandler for the uploading tasks, HttpClient can be used to send the data to server via HTTP.
123456789101112131415161718
finalHttpClienthttpClient=newDefaultHttpClient();finalHttpPosthttpPost=newHttpPost(url);httpPost.addHeader("Content-Type","application/x-gzip");httpPost.addHeader("Content-Encoding","gzip");httpPost.setEntity(newByteArrayEntity(dataEncrypted));try{finalHttpResponseresponse=httpClient.execute(httpPost);finalintstatusCode=response.getStatusLine().getStatusCode();if(statusCode==200){// do things}else{// handle failure}}catch(Exceptione){Log.w(TAG,"Error occured during data sending, abort reason: "+e.getMessage());// handle failure}
In iOS 6, Apple provides ADID, we could use it to identify people, but app user can switch it off if they don’t want to be tracked.
I use OpenUDID and ADID to identify app user, what OpenUDID does is generating a UUID and caching it for later look up.
In Android, it has IMEI, ANDROID_ID, and mac address, none of them is reliable, so I hash those three with device model use SHA to identify the app user.
// Android UUID known to be duplicated across many devices due to manufacturer bugspublicstaticfinalStringINVALID_ANDROID_ID="9774d56d682e549c";publicstaticStringgetUDID(Contextcontext){Stringimei="";if(context.getPackageManager().checkPermission(Manifest.permission.READ_PHONE_STATE,context.getPackageName())==PackageManager.PERMISSION_GRANTED){TelephonyManagertm=(TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);imei=tm.getDeviceId();}StringandroidId=Settings.Secure.getString(context.getContentResolver(),android.provider.Settings.Secure.ANDROID_ID);if(androidId==null||androidId.toLowerCase().equals(INVALID_ANDROID_ID)){androidId="";}StringmacAddr=(getMacAddr(context)==null)?"":getMacAddr(context);returnSHA(imei+androidId+macAddr+Build.MODEL);}publicstaticStringgetMacAddr(Contextcontext){StringmacAddr=null;if(context.getPackageManager().checkPermission(Manifest.permission.ACCESS_WIFI_STATE,context.getPackageName())==PackageManager.PERMISSION_GRANTED){finalWifiManagermanager=(WifiManager)context.getSystemService(Context.WIFI_SERVICE);finalWifiInfoinfo=manager.getConnectionInfo();if(null!=info){macAddr=info.getMacAddress();}}else{// no permission}returnmacAddr;}staticStringSHA(finalStringtoConvert){try{finalMessageDigestmd=MessageDigest.getInstance("SHA");finalbyte[]digest=md.digest(toConvert.getBytes("UTF-8"));finalBigIntegerhashedNumber=newBigInteger(1,digest);returnhashedNumber.toString(16);}catch(finalNoSuchAlgorithmExceptione){thrownewRuntimeException(e);}catch(finalUnsupportedEncodingExceptione){thrownewRuntimeException(e);}}
Last week, I read the old book Get Things Done again, it did make me think. I read it first two years ago, at that time, I didn’t feel the benefits since I could manage all stuff in my mind. But now, I can’t, I do forget things, I do procrastinate things. During my reread, I kept asking me, why I just missed this treasure, so silly I was.
Below are my reading notes.
Stuff: anything you have allowed into your psychological or physical world that doesn’t belong where it is, but for which you haven’t yet determined the desired outcome and the next action step.
We need to transform all the “stuff” we’re trying to organize into actionable stuff we need to do.
Five-stage methods:
Collect things that command our attention
Process what they mean and what to do about them
Organize the results
Review as options for what we choose to
Do
Collect
In Box requirements:
Every open loop must be in your collection system and out of your mind
You must have as few collection buckets as you can get by with
You must empty their regularly
Process - Organize
Process guidelines:
Process the top item first
Process one item at a time
Never put anything back into “In Box”
Review
Review your lists as often as you need to, to get them off your mind.
Review Flow: Calendar - Next Actions - Projects - Waiting for - Someday/Maybe
Weekly Review:
Gather and process all your “stuff”
Review your system
Update your lists
Get clean, clear, current, and complete
Do
Model for choosing actions:
Context (I prefer Context over Project)
Time available
Energy available
Priority
Model for evaluating daily work
Doing predefined work
Doing work as it shows up
Defining your work
Model for reviewing your own work
Life
Three-to five-year vision
One-to two-year goals
Areas of responsibility
Current projects
Current actions
Project Planning
Planning Steps:
Defining purpose and principles
Outcome visioning
Brainstorming
Organizing
Identifying next actions
If you’re waiting to have a good idea before you have any ideas, you won’t have many ideas.
Benefits of asking “why?”:
It defines success
It creates decision-making criteria
It aligns resources
It motivates
It clarifies focus
It expands options
Developing a vision:
View the project from beyond the completion date
Envision “wild success”
Capture features, aspects, qualities you imagine in place
Brainstorming Keys:
Don’t judge, challenge, evaluate, or criticize
Go for quantity, not quality
Put analysis and organization in the background
Organizing steps:
Identify the significance pieces
Sort by (one or more): components, sequences, priorities, details to the required degree
Like last year, I feel 2012 is much shorter than previous years.
During this year, I worked on things that I had to but thought stupid, things that I did and deeply enjoyed, and things that I had waited long and finally did it.
I almost always work alone, I could freely choose languages and tools to work on, e.g. use Java for web crawlers, python to run hadoop streaming map-reduce, Objective C for iOS programs, and even use Intellij IDEA to write Android programs. I really did enjoy it!
Last year, I said I like agile. If I’m working on NASA, I might probably bear the waterfall shit, but, now I found out that it’s even worse when you do waterfall, but don’t believe you are doing waterfall.
I also met a guy, maybe you are familiar too. Some day he comes, and ask for why you didn’t do some x in y, you answered, it is because of z, and let p in will make it …, like always, he didn’t let you finish, said he get it. The truth is, you had no idea of what the fuck you were talking about. Then the question is, why “people” (might including me) asks questions that he don’t know what’s right or wrong and pretend he knows, I believe it’s a worldwide “hard” question.
Last year, I wrote 22 blogs, and this year is 17, I still can’t get rid of the procrastination syndrome, often had some ideas to write, but thought maybe wait two or three days, and then never. Writing blog posts is not only about sharing knowledge, but also organizing thoughts, refactoring knowledge, and learning more, which is the most important part.
I never thought that the imperfection of a product turns to be a great benefit. Kindle, and its super slow touch response, letting me concentrate on one only thing, READ.
Hope next year, not only read more books, but also write more book reviews.
The two programming languages I’ve learned this year are: Ruby & Scheme. What about next year? Maybe go and erlang.
At the end of this year, the communist party of China issued new rules on internet control, worse than ever. I don’t like the government, the super rich princelings, the polluted air, the house price …, I thought about leave, but I don’t think I will, at the end of this year, I am seriously considering.