2013年5月22日水曜日

Google I/O 2013 - Android : Android Protips: Making Apps Work Like Magic

Android Protips: Making Apps Work Like Magic

導入部分の話が面白い
  • ムーアの法則
  • 昔の PC の話とか
  • 昔のネット通信のモデムの音とか
  • アーサー・C・クラークの言葉
    "Any significantly advanced technology is indistinguishable from magic." "高度に発達したテクノロジーは魔法と見分けがつかない"
  • 5年前まだ Android がないときにブログに書いた未来の予想が悲観的すぎた、予想よりも早く実現しつつある
  • アーサー・C・クラークの言葉その2
    "The only way of discovering the limits of the possible is to venture a little way past it into the impossible."
スポーツで相手の進行方向を予測してパスを出すように、リリース時の状況を予測しよう(ライバルは今よりアップグレードしてるだろう、デバイスの分布はどうだろう。。。)

Android Beam の話
# NFC の機能が魔法っぽいってはなしかな

Lockscreen Widget
# Lockscreen Widget って魔法っぽいかな。。。?

"Context isn't important, it's critical."

デバイスはユーザーのことを知っている。デバイスはどんなニュースや本を読んで、どんな音楽を聴いて、どんな映画を見て、どんなゲームをして、だれが友達で、どこで予定があるのか知っている
魔法のような体験を作るための Context を用意する、すごい可能性がここにはある

どんな Context が使える?
例えば Location

Location based Services

Google Play Services の一部として新しい Location based Services をリリースしたよ!
すごい簡単に使えるよ
単にクライアントを作って接続すれば OK private void connectLBS() { int gpsExists = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); if(gpsExists == ConnectionResult.SUCCESS) { mLocationClient = new LocationClient(this, this, this); mLocationClient.connect(); } } @Override public void onConnected(Bundle connectionHint) { requestUpdates(mLocationClient); } どの provider (位置の)が有効かどうかチェックする必要はない
Google Play Services の一部である Fused Location Provider が最もいい結果を返してくれる

Geofencing List<Geofence> fenceList = new ArrayList<Geofence>(); // TODO Repeat for all Geofences Geofence geofence = new Geofence.Builder() .setRequestId(mKey) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) .setCircularRegion(latitude, longitude, GEOFENCE_RADIUS) .setExpirationDuration(Geofence.NEVER_EXPIRE) .build(); fenceList.add(geofence); mLocationClient.addGeofences(fenceList, pendingIntent, addGeofenceResultListener);

Activity Recognition Intent intent = new Intent(this, ActivityRecognitionIntentService.class); intent.setAction(MyActivity.ACTION_STRING); PendingIntent pi = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mActivityRecognitionClient.requestActivityUpdates(interval, pi); @Override protected void onHandleIntent(Intent intent) { if(intent.getAction() == MyActivity.ACTION_STRING) { if(ActivityRecognitionResult.hasResult(intent)) { ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent); DetectedActivity detectedActivity = result.getMostProbableActivity(); int activityType = detectedActivity.getType(); if (activityType == DetectedActivity.STILL) setUpdateSpeed(PAUSED); else if (activityType == DetectedActivity.IN_VEHICLE) setUpdateSpeed(FASTER); else setUpdateSpeed(REGULAR); } } }


Google+ について

友達の状態とかがとれるのでもっと Context にあったことができるという話
(どうなんでしょうね。。。)

知りすぎると Uncanny App Valley に落ちるよ
Google Now は何をするのかはっきりわかるけど、もしソフトキーボードが自分の好きなスポーツを勧めてきたりしたら気持ち悪いよねってこと

Rob Foster の言葉
"Introducing visceral elements into an app.. will make it speak to the subconscious."


人生は単になにをするかだけではなく、何を体験するかだから

カフェで人は単にコップのなかの水を買っているわけではなく。その周りにひろがる体験(雰囲気とか香りとか見た目とかもろもろ)を買っている

アプリでは何ができる?
・テイストはだめだよね
・香り、、、もだめだよね、来年どうなるかみよう
ということで
・見た目
・音
・タッチ
にフォーカスしよう

背後にある哲学、スピリット、ビジョンを理解しよう
https://developer.android.com/design をみよう


Text to Speech(TTS)

セットアップは簡単 private void initTextToSpeech() { Intent intent = new Intent(Engine.ACTION_CHECK_TTS_DATA); startActivityForResult(intent, TTS_DATA_CHECK); } @Override protected void onActivityResult(int request, int result, Intent data) { if(request == TTS_DATA_CHECK && result == Engine.CHECK_VOICE_DATA_PASS) { tts = new TextToSpeech(this, new OnInitListener() { @Override public void onINit(int status) { if (status == TextToSpeech.CUSSCESS) ttsIsInit = true; } }); } else { startActivity(new Intent(Engine.ACTION_INSTALL_TTS_DATA); } } private void say(String text) { if (tts != null && ttsIsInit) { tts.speak(text, TextToSpeech.QUEUE_ADD, null); } } “そのアプリ”を使う理由になる機能をいれよう


Speech Recognition private void requestVoiceInput() { Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.voice_input_prompt); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.ENGLISH); startActivityForResult(intent, VOICE_RECOGNITION); } @Override protected void onActivityResult(int request, int result, Intent data) { if (request == VOICE_RECOGNITION && result == RESULT_OK) { ArrayList results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); String mostLikelyResult = results[0]; useSpeechInput(mostLikelyResult); } } ここでは、もっともありえそうだとシステムが認識した答え(スタックの一番最初)をつかっているけど、Context を利用して候補のなかからより正しいものを選択するべき


48dip のレイアウト

どれがタッチできるのかユーザーがわかるようにする = ちゃんと state に応じた画像を用意しよう

android:foreground="?android:selectableItemBackground"


Simple Accessibility Support <Button ... android:contentDescription="@string/my_button_description" />


Custom Control Accessibility Support public void setHeading(float heading) { mHeading = heading; sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); } @Override public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent e) { super.dispatchPopulateAccessibilityEvent(e); String heading = String.valueOf(mHeading); if(heading.length() > AccessibilityEvent.MAX_TEXT_LENGTH) { heading = heading.subString(0, AccessibilityEvent.MAX_TEXT_LENGTH); } event.getText().add("Heading is " + heading + " degree"); return true; }


ジェスチャーを使う Android Training の Using Touch Gestures クラスがおすすめ

Jazz Hands @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); if (event.getPointerCount() > 1) { int actionPointerId = action & MotionEvent.ACTION_POINTER_ID_MASK; int actionEvent = action & MotionEvent.ACTION_MASK; int pointerIndex = event.findPointerIndex(actionPointerId); int xPos = (int) event.getX(pointerIndex); int yPos = (int) event.getY(pointerIndex); // TODO Magic. } }

ルールを破るときは気をつけて、かならずしも positive な反応になるとは限らない
→ Google Play の developer console に追加された ALPHA TESTING, BETA TESTING 機能を使おう

だれがもっともバリューゾーンのユーザーなのかを知ろう、国、言語、デバイス、、、
→ Analytics を使おう

# FFっぽい動画でてきたでw





# Google Quest Cheat Codes...w




スマートフォンの一番の魔法は常にネットに繋がっていることじゃないかな
更新ボタンを eliminate するのはやめよう
ユーザーが更新しようとする前に更新しといてほしいよね

データのやりとりの理由としては
  • Client Updates
  • Server Updates
  • On-Demand Downloads
  • Cross-Device Updates
サーバー側の update については Google Cloud Messaging という強力なツールがある
Cross-Device updates にも GCM が使えるよ
キーノートのデモであったように Notification がデバイス間で同期するようになったよ


Google Cloud Messaging: Upstream GoogleCloudMessaging gcm = GoogleCloudMessaging.get(context); gcm.send(to, msgId, data); サーバー側がよくわからない? Mobile Backend Starter を使おう


Client Updates にとてもいいものがあるよ、それが SyncAdapter public class MySyncAdapter extends AbstractThreadSyncAdapter { public MySyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { super(context, autoInitialize, allowParallelSyncs); } public MySyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderCLient provider, SyncResult syncResult) { // TODO Synchronize your data between client adn server. } } # ネットが繋がっていないと繋がったときに処理をするよう待ってくれたりするらしい


Abstract Account Manager public class MyAccountAuthenticator extends AbstractAccountAuthenticator { public static final String ACCOUNT_TYPE = "com.mycompany.myapp"; public static final String ACCOUNT_NAME = "MY STUB ACCOUNT"; @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bndle options) throw NetworkErrorException { AccountManager manager = AccountManager.get(activity); final Account account = new Account(ACCOUNT_NAME, ACCOUNT_TYPE); manager.addAccountExplicitly(account, null, null); ContentResolver.setIsSyncable(account, authority, 1); ContentResolver.setSyncAutomatically(account, authority, true); return null; } ... } アカウントタイプに気をつける



Account Manager Service public class MyAuthenticationService extends Service { MyAccountAuthenticator mAuthenticator; @Override public void onCreate() { mAuthenticator = new MyAccountAuthenticator(this); } @Override public IBinder onBind(Intent intent) { return mAuthenticator.getIBinder(); } } Account Manager XML Config <account-authenticator xmlns:android="..." android:accountType="com.mycompany.myapp" android:icon="@drawable/icon" android:smallIcon="@drawable/miniicon" android:label="@string/app_name" /> Content Provider public class MyContentProvider extends ContentProvider { @Override public boolean onCreate() { return true; } @Override public String getType(Uri uri) { return "vnd.android.cursor.dir/vnd.myapp.items"; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) { return null; } @Override public Uri insert(Uri uri, ContentValues initialValues) { return null; } @Override public int delete(Uri uri, String where, String[] whereArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { return 0; } } Manifest <provider android:authorities="com.mycompany.myapp.myauthority" android:name=".content_providers.PlaceDetailsContentProvider" /> Sync Adapter XML Config <sync-adapter xmlns:android="..." android:contentAuthority="com.mycompany.myapp.myauthority" android:accountType="com.mycompany.myapp" android:userVisible="false" /> Manifest <provider android:authorities="com.mycompany.myapp.myauthority" android:name=".content_providers.PlaceDetailsContentProvider" /> <service android:name=".MyAuthenticationService" android:exported="true"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/accountauth" /> </service> <service android:name=".MySyncService" android:exported="true"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_myapp" /> </service> Triggering Syncs final Account account = new Account(null, MyAccountAuthenticator.ACCOUNT_TYPE); String authority = "com.mycompany.myapp.myauthority"; mContentResolver.requestSync(account, authority, null); Periodic Repeating Syncs final Account account = new Account(null, MyAccountAuthenticator.ACCOUNT_TYPE); String authority = "com.mycompany.myapp.myauthority"; long interval = 12 * 60 * 60; // 12 hours (24hr by default). mContentResolver.addPeriodicSync(account, authority, null, interval); repeating syncs にする理由は、もしサーバーの update が毎日2時にあるとして、そこにアラームをかけてクライアントが同期するようにすると、その時間サーバーにすごい負荷がかかってしまう

時間をベースにして同期はやめよう
たとえネットに接続しなくても次のようにランダムな jitter を使おう


Update Window final Account account = new Account(null, MyAccountAuthenticator.ACCOUNT_TYPE); String authority = "com.mycompany.myapp.myauthority"; Random random = new Random(); int jitter = random.nextInt(60); long start = SystemClock.elapseRealtime() + interval - ((30 + jitter)*60000); alarmManager.setInexactRepeating(alamType, start, interval, pi); Acitvity Recognition: Pickup Trigger @Override public void onReceive(Context context, Intent intent) { // Extract the Activity that's been detected. ActivityDetectionResult activity = ActivityDetectionResult.extractResult(intent); if(activity != null) { ActivityType activityType = activity.getMostProvavleActivity().getType(); if (ActivityType.valueOf(activityTypeString) == ActivityType.TILTING) context.startService(new Intent(context, DailyUpdateService.class); } }


On-demand download

SyncAdapter with Calls to Other Updates @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult result) { downloadServerSideSync(); uploadClientSideSync(); transmitBatchedAnalytics(); executePrefetch(); retryFiledTransfers(); }




# 1時間という長いセッションだったし、最後のほうはコードが多かったな # 3番目の動画がいいね



0 件のコメント:

コメントを投稿