Top Banner
Android最速フォトグラファーに 俺はなる!! @kassy_kz for yapf 2014 / 5 / 3
52

Android最速のフォトグラファーに、俺はなる!

Jun 20, 2015

Download

Technology

2014/5/3 横浜PF部で発表したスライドです。
カメラの起動高速化について真面目に考えてみた様子を記しています。
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Android最速のフォトグラファーに、俺はなる!

Android最速の フォトグラファーに 俺はなる!!

@kassy_kz

for yapf 2014 / 5 / 3

Page 2: Android最速のフォトグラファーに、俺はなる!

最速になりたい

Page 3: Android最速のフォトグラファーに、俺はなる!

自己紹介

• 名前:@kassy_kz (かっしー)

• 二つ名:神奈川の健全王(自称)    多摩川の直線鬼(自称)

• 趣味:カメラ   (フォトマスター1級所持)

Page 4: Android最速のフォトグラファーに、俺はなる!

フォトグラファーにとって 最も重要なファクターとは

Page 5: Android最速のフォトグラファーに、俺はなる!

それはスピード※個人の意見です

Page 6: Android最速のフォトグラファーに、俺はなる!

一瞬のシャッターチャンスを逃さない

それこそが真のフォトグラファー※あくまで個人の意見です

Page 7: Android最速のフォトグラファーに、俺はなる!

つまり必要なのは スピード!

Page 8: Android最速のフォトグラファーに、俺はなる!

Androidでカメラの起動速度を極める

最速のフォトグラファーを目指して

Page 9: Android最速のフォトグラファーに、俺はなる!

通常の、カメラ起動までの流れ

2~5ステップ

Page 10: Android最速のフォトグラファーに、俺はなる!

これではチャンスを逃してしまう…

Page 11: Android最速のフォトグラファーに、俺はなる!

端末の状態に関わらず ワンステップでアプリを起動する

目標

Page 12: Android最速のフォトグラファーに、俺はなる!

Google Nowの起動シーケンスに着目

端末がどんな状態であろうと 「ホームボタン上スライド」

の1アクションで Google Nowが起動する

Page 13: Android最速のフォトグラファーに、俺はなる!

ヨコが余ってんじゃね?

このサークルのヨコに アイテムを追加できれば

「ホームボタン横スライド」 のワンアクションで

アプリを起動できるのでは!?

Page 14: Android最速のフォトグラファーに、俺はなる!

よろしい !

ならば(ROMの)改造だ

Page 15: Android最速のフォトグラファーに、俺はなる!

着目するのは PhoneStatusBar

この部分は PhoneStatusBarというところで実装されている

パスは/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/

Page 16: Android最速のフォトグラファーに、俺はなる!

PhoneStatusBarのコードを読む

       private  void  prepareNavigationBarView()  {       (中略)                mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener);                updateSearchPanel();          }  

PhoneStatusBar.java

↑ Homeボタンをおした時のリスナーを登録してる箇所を発見!

       View.OnTouchListener  mHomeSearchActionListener  =  new  View.OnTouchListener()  {                  public  boolean  onTouch(View  v,  MotionEvent  event)  {              (中略)                          mHandler.postDelayed(mShowSearchPanel,  mShowSearchHoldoff);               (中略)        };  

PhoneStatusBar.java

↑ リスナーの実装はこれ、mShowSearchPanelメソッドを呼び出している!

Page 17: Android最速のフォトグラファーに、俺はなる!

PhoneStatusBarのコードを読む

       @Override          public  void  showSearchPanel()  {                  int  msg  =  MSG_OPEN_SEARCH_PANEL;                  mHandler.removeMessages(msg);                  mHandler.sendEmptyMessage(msg);          }  

BaseStatusBar.java (PhoneStatusBarの基底クラス)

↑ handlerにメッセ投げている

       protected  class  H  extends  Handler  {                  public  void  handleMessage(Message  m)  {                          switch  (m.what)  {                            case  MSG_OPEN_SEARCH_PANEL:                                    if  (DEBUG)  Log.d(TAG,  "opening  search  panel");                                    if  (mSearchPanelView  !=  null  &&  mSearchPanelView.isAssistantAvailable())  {                                            mSearchPanelView.show(true,  true);                                            onShowSearchPanel();                                    }                                    break;                          }                  }          }  

↑ handlerの受け側。SearchPanelViewのshow() メソッドをコールしている

Page 18: Android最速のフォトグラファーに、俺はなる!

Homeキー押下で出現するこのViewは SearchPanelViewと言うらしい

Page 19: Android最速のフォトグラファーに、俺はなる!

更にSearchPanelViewのコードを読む

       @Override          protected  void  onFinishInflate()  {                    (中略)                  mGlowPadView  =  (GlowPadView)  findViewById(R.id.glow_pad_view);                  mGlowPadView.setOnTriggerListener(mGlowPadViewListener);          }  

SearchPanelView.java

↑ 入れ子にGlowPadViewというViewが貼られている事がわかる。

   public  GlowPadView(Context  context,  AttributeSet  attrs)  {                  (中略)                  if  (a.getValue(R.styleable.GlowPadView_targetDrawables,  outValue))  {                          internalSetTargetResources(outValue.resourceId);                  }  

GlowPadView.java

↑ 独自カスタムViewの書き方をしている。 XMLファイルの「targetDrawables」というところから値を取ってきている事がわかる internalSetTargetResources以下では、drawbleの配置を行っている。

Page 20: Android最速のフォトグラファーに、俺はなる!

さらに読む(そろそろ疲れてきた)

      prvandroid:targetDrawables="@array/navbar_search_targets”"

       <array  name="navbar_search_targets">                  <item>@null</item>                  <item>@*android:drawable/ic_action_assist_generic</item>                  <item>@null</item>                  <item>@null</item>          </array>

/frameworks/base/packages/SystemUI/res/layout/status_bar_search_panel.xml

↑ array/navbar_search_targetsというところを参照している

/frameworks/base/packages/SystemUI/res/values-land/arrays.xml

↑ drawable4つを格納する配列、   そして1つを除いてnullが格納されているという謎の状態

Page 21: Android最速のフォトグラファーに、俺はなる!

drawableを埋めてみた

       <array  name="navbar_search_targets">                  <item>@*android:drawable/ic_lockscreen_camera_activated</item>                  <item>@*android:drawable/ic_action_assist_generic</item>                  <item>@*android:drawable/ic_lockscreen_camera_activated</item>                  <item>@*android:drawable/ic_lockscreen_camera_activated</item>          </array>

/frameworks/base/packages/SystemUI/res/values-land/arrays.xml

↑nullの箇所に自分で持ってきたdrawableのIDを埋め込んでみた

画像は以下の場所に良さげなのがあったので流用 /frameworks/base/packages/Keyguard/res/ drawable-xhdpi/ic_lockscreen_camera_activated.png

Page 22: Android最速のフォトグラファーに、俺はなる!

追加画像がよしなに配置される!

明らかにヨコへの配置を前提にしたコードだった 将来のバージョンアップによってはもしかしたら…

Page 23: Android最速のフォトグラファーに、俺はなる!

アクションの取得はどうやってるのか

       class  GlowPadTriggerListener  implements  GlowPadView.OnTriggerListener  {                      public  void  onTrigger(View  v,  final  int  target)  {                          final  int  resId  =  mGlowPadView.getResourceIdForTarget(target);                              switch  (resId)  {                                  case  com.android.internal.R.drawable.ic_action_assist_generic:                                          mWaitingForLaunch  =  true;                                          startAssistActivity();                                          vibrate();                                          break;                          }                  }  

SearchPanelView.java

Page 24: Android最速のフォトグラファーに、俺はなる!

SearchPanelView

GlowPadView

アクションの取得はどうやってるのか

リスナー登録 コールバック(引数はdrawableID)

SearchPanelView

GlowPadView

Page 25: Android最速のフォトグラファーに、俺はなる!

カメラ起動コードを埋め込む

       class  GlowPadTriggerListener  implements  GlowPadView.OnTriggerListener  {                      public  void  onTrigger(View  v,  final  int  target)  {                          final  int  resId  =  mGlowPadView.getResourceIdForTarget(target);                              switch  (resId)  {                                  case  com.android.internal.R.drawable.ic_action_assist_generic:                                          mWaitingForLaunch  =  true;                                          startAssistActivity();                                          vibrate();                                          break;                                      case  17302260:                                          final  Intent  intent  =  new  Intent();                                          postDelayed(new  Runnable()  {                                                  public  void  run()  {                                                          intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                                                          intent.setAction("android.media.action.IMAGE_CAPTURE");                                                          mContext.startActivityAsUser(intent,                                                                        new  UserHandle(UserHandle.USER_CURRENT));                                                  }                                          },  SEARCH_PANEL_HOLD_DURATION);                                          break;                          }                  }  

SearchPanelView.java

追加

←自分で追加したdrawableのID

Page 26: Android最速のフォトグラファーに、俺はなる!

デモ

Page 27: Android最速のフォトグラファーに、俺はなる!

神奈川の健全王が シャッターチャンスに遭遇してから シャッター押下までのタイムは わずか0.8秒 (自分調べ) にすぎない

Page 28: Android最速のフォトグラファーに、俺はなる!

では、 その撮影プロセスを もう一度見てみよう

Page 29: Android最速のフォトグラファーに、俺はなる!

カメラ起動まで常にワンアクション!

Page 30: Android最速のフォトグラファーに、俺はなる!

これで決まりだ!

Page 31: Android最速のフォトグラファーに、俺はなる!

上司ゴースト

大体最初から ユースケースの整理が

配慮がたらんわ バカもんが

端末がスリープ状態のときはどうするんだ?

あ?

Page 32: Android最速のフォトグラファーに、俺はなる!

orz

Page 33: Android最速のフォトグラファーに、俺はなる!

端末スリープ状態から ワンアクションでカメラを起動する

次の目標

Page 34: Android最速のフォトグラファーに、俺はなる!

イヤホンのボタンに着目

コレ

端末がスリープ状態でも ボタンを押せば音楽の再生停止ができる

Page 35: Android最速のフォトグラファーに、俺はなる!

これを使えば スリープ状態からでも

なんらかのアクションを起こせるのでは

問題はどうやってマイクボタン押下を検出するかだ

Page 36: Android最速のフォトグラファーに、俺はなる!

ボタン押した時のログを見てみる

04-13 12:51:14.386: W/ContextImpl(28640): Implicit intents with startService are not safe: Intent { act=com.google.android.music.NETWORK_MONITOR_SERVICE } android.content.ContextWrapper.bindService:517 com.google.android.music.utils.SafeServiceConnection$ServiceConnectionImp.bindService:98 com.google.android.music.utils.SafeServiceConnection.bindService:259 04-13 12:51:14.406: D/SystemUtils(28640): getSystemProperty: lpa.decode=null 04-13 12:51:14.406: I/MediaFocusControl(771): Remote Control registerMediaButtonIntent() for PendingIntent{4313edb0: PendingIntentRecord{431d35c8 com.google.android.music broadcastIntent}} 04-13 12:51:14.416: V/Avrcp(2343): New genId = 6, clearing = 1 04-13 12:51:14.416: V/Avrcp(2343): New genId = 7, clearing = 1 04-13 12:51:14.416: I/LocalDevicePlayback(28640): play: currentPos=-1 04-13 12:51:14.416: I/MediaFocusControl(771): AudioFocus requestAudioFocus() from android.media.AudioManager@426b6090com.google.android.music.playback.LocalDevicePlayback$9@42736830 04-13 12:51:14.416: V/Avrcp(2343): New genId = 8, clearing = 0 04-13 12:51:14.416: D/CacheService(28640): onCreate 04-13 12:51:14.416: D/CacheService(28640): onBind 04-13 12:51:14.426: W/ContextImpl(28640): Implicit intents with startService are not safe: Intent { act=com.google.android.music.NETWORK_MONITOR_SERVICE } android.content.ContextWrapper.bindService:517 com.google.android.music.utils.SafeServiceConnection$ServiceConnectionImp.bindService:98 com.google.android.music.utils.SafeServiceConnection.bindService:259 04-13 12:51:14.426: I/LocalDevicePlayback(28640): Streaming client created. 04-13 12:51:14.456: I/LocalDevicePlayback(28640): stop: willPlay=false, currentPos=20 04-13 12:51:14.466: V/Avrcp(2343): updatePlayPauseState, old=0, state=0 04-13 12:51:14.466: V/Avrcp(2343): position=0 04-13 12:51:14.466: V/Avrcp(2343): mMetadata=Metadata[artist=null trackTitle=null albumTitle=null] 04-13 12:51:14.466: V/Avrcp(2343): duration=0 04-13 12:51:14.466: V/Avrcp(2343): updatePlayPauseState, old=0, state=3 04-13 12:51:14.466: V/Avrcp(2343): position=0 04-13 12:51:14.476: I/LocalDevicePlayback(28640): open: id=[19, DEFAULT], pos=0, playPos=20, fromUser=true, track=to the beginning 04-13 12:51:14.486: D/dalvikvm(28640): GC_CONCURRENT freed 322K, 2% free 17459K/17812K, paused 2ms+1ms, total 12ms 04-13 12:51:14.486: D/LocalAsyncMediaPlayer(28640): Event logging MUSIC_START_PLAYBACK_REQUESTED: 19/: local playback 04-13 12:51:14.536: D/dalvikvm(28640): GC_FOR_ALLOC freed 33K, 2% free 17605K/17960K, paused 13ms, total 13ms 04-13 12:51:14.546: D/dalvikvm(28640): GC_FOR_ALLOC freed 2K, 2% free 17749K/18108K, paused 12ms, total 13ms 04-13 12:51:14.566: D/dalvikvm(28640): GC_FOR_ALLOC freed 471K, 5% free 17456K/18256K, paused 12ms, total 12ms 04-13 12:51:14.586: D/dalvikvm(28640): GC_CONCURRENT freed 3K, 3% free 17878K/18256K, paused 2ms+1ms, total 15ms 04-13 12:51:14.606: D/dalvikvm(28640): GC_FOR_ALLOC freed 9K, 3% free 17879K/18256K, paused 9ms, total 9ms 04-13 12:51:14.606: I/dalvikvm-heap(28640): Grow heap (frag case) to 17.768MB for 294928-byte allocation 04-13 12:51:14.616: D/dalvikvm(28640): GC_FOR_ALLOC freed <1K, 3% free 18167K/18548K, paused 10ms, total 10ms 04-13 12:51:14.616: V/Avrcp(2343): updatePlayPauseState, old=3, state=3 04-13 12:51:14.616: V/Avrcp(2343): position=170455 04-13 12:51:14.626: I/ActivityManager(771): Start proc com.android.musicfx for broadcast com.android.musicfx/.ControlPanelReceiver: pid=8691 uid=10013 gids={50013, 3003, 3002} 04-13 12:51:14.636: I/AudioFlinger(183): HAL output buffer size 32768 frames, normal mix buffer size 32768 frames 04-13 12:51:14.636: I/AudioFlinger(183): AudioFlinger's thread 0xb7fc0ce8 ready to run 04-13 12:51:14.636: D/dalvikvm(181): GC_EXPLICIT freed 42K, 1% free 16750K/16824K, paused 1ms+2ms, total 15ms 04-13 12:51:14.636: W/AudioFlinger(183): moveEffectChain_l() effect chain for session 0 not on source thread 0xb5ba1008

とりあえず

Page 37: Android最速のフォトグラファーに、俺はなる!

ボタン押した時のログを見てみる

04-13 12:51:14.386: W/ContextImpl(28640): Implicit intents with startService are not safe: Intent { act=com.google.android.music.NETWORK_MONITOR_SERVICE } android.content.ContextWrapper.bindService:517 com.google.android.music.utils.SafeServiceConnection$ServiceConnectionImp.bindService:98 com.google.android.music.utils.SafeServiceConnection.bindService:259 04-13 12:51:14.406: D/SystemUtils(28640): getSystemProperty: lpa.decode=null 04-13 12:51:14.406: I/MediaFocusControl(771): Remote Control registerMediaButtonIntent() for PendingIntent{4313edb0: PendingIntentRecord{431d35c8 com.google.android.music broadcastIntent}} 04-13 12:51:14.416: V/Avrcp(2343): New genId = 6, clearing = 1 04-13 12:51:14.416: V/Avrcp(2343): New genId = 7, clearing = 1 04-13 12:51:14.416: I/LocalDevicePlayback(28640): play: currentPos=-1 04-13 12:51:14.416: I/MediaFocusControl(771): AudioFocus requestAudioFocus() from android.media.AudioManager@426b6090com.google.android.music.playback.LocalDevicePlayback$9@42736830 04-13 12:51:14.416: V/Avrcp(2343): New genId = 8, clearing = 0 04-13 12:51:14.416: D/CacheService(28640): onCreate 04-13 12:51:14.416: D/CacheService(28640): onBind 04-13 12:51:14.426: W/ContextImpl(28640): Implicit intents with startService are not safe: Intent { act=com.google.android.music.NETWORK_MONITOR_SERVICE } android.content.ContextWrapper.bindService:517 com.google.android.music.utils.SafeServiceConnection$ServiceConnectionImp.bindService:98 com.google.android.music.utils.SafeServiceConnection.bindService:259 04-13 12:51:14.426: I/LocalDevicePlayback(28640): Streaming client created. 04-13 12:51:14.456: I/LocalDevicePlayback(28640): stop: willPlay=false, currentPos=20 04-13 12:51:14.466: V/Avrcp(2343): updatePlayPauseState, old=0, state=0 04-13 12:51:14.466: V/Avrcp(2343): position=0 04-13 12:51:14.466: V/Avrcp(2343): mMetadata=Metadata[artist=null trackTitle=null albumTitle=null] 04-13 12:51:14.466: V/Avrcp(2343): duration=0 04-13 12:51:14.466: V/Avrcp(2343): updatePlayPauseState, old=0, state=3 04-13 12:51:14.466: V/Avrcp(2343): position=0 04-13 12:51:14.476: I/LocalDevicePlayback(28640): open: id=[19, DEFAULT], pos=0, playPos=20, fromUser=true, track=to the beginning 04-13 12:51:14.486: D/dalvikvm(28640): GC_CONCURRENT freed 322K, 2% free 17459K/17812K, paused 2ms+1ms, total 12ms 04-13 12:51:14.486: D/LocalAsyncMediaPlayer(28640): Event logging MUSIC_START_PLAYBACK_REQUESTED: 19/: local playback 04-13 12:51:14.536: D/dalvikvm(28640): GC_FOR_ALLOC freed 33K, 2% free 17605K/17960K, paused 13ms, total 13ms 04-13 12:51:14.546: D/dalvikvm(28640): GC_FOR_ALLOC freed 2K, 2% free 17749K/18108K, paused 12ms, total 13ms 04-13 12:51:14.566: D/dalvikvm(28640): GC_FOR_ALLOC freed 471K, 5% free 17456K/18256K, paused 12ms, total 12ms 04-13 12:51:14.586: D/dalvikvm(28640): GC_CONCURRENT freed 3K, 3% free 17878K/18256K, paused 2ms+1ms, total 15ms 04-13 12:51:14.606: D/dalvikvm(28640): GC_FOR_ALLOC freed 9K, 3% free 17879K/18256K, paused 9ms, total 9ms 04-13 12:51:14.606: I/dalvikvm-heap(28640): Grow heap (frag case) to 17.768MB for 294928-byte allocation 04-13 12:51:14.616: D/dalvikvm(28640): GC_FOR_ALLOC freed <1K, 3% free 18167K/18548K, paused 10ms, total 10ms 04-13 12:51:14.616: V/Avrcp(2343): updatePlayPauseState, old=3, state=3 04-13 12:51:14.616: V/Avrcp(2343): position=170455 04-13 12:51:14.626: I/ActivityManager(771): Start proc com.android.musicfx for broadcast com.android.musicfx/.ControlPanelReceiver: pid=8691 uid=10013 gids={50013, 3003, 3002} 04-13 12:51:14.636: I/AudioFlinger(183): HAL output buffer size 32768 frames, normal mix buffer size 32768 frames 04-13 12:51:14.636: I/AudioFlinger(183): AudioFlinger's thread 0xb7fc0ce8 ready to run 04-13 12:51:14.636: D/dalvikvm(181): GC_EXPLICIT freed 42K, 1% free 16750K/16824K, paused 1ms+2ms, total 15ms 04-13 12:51:14.636: W/AudioFlinger(183): moveEffectChain_l() effect chain for session 0 not on source thread 0xb5ba1008

アヤシイ

I/MediaFocusControl(771): Remote Control registerMediaButtonIntent() for PendingIntent{4313edb0: PendingIntentRecord{431d35c8

とりあえず

Page 38: Android最速のフォトグラファーに、俺はなる!

Androidソースコードから検索

MediaFocusControl#registerMediaButtonIntent

AudioManager#registerMediaButtonEventReceiver↑

この辺りが怪しい

Page 39: Android最速のフォトグラファーに、俺はなる!

調べたらフツーにリファレンスが出てきた…

http://developer.android.com/training/managing-audio/volume-playback.html

Page 40: Android最速のフォトグラファーに、俺はなる!

イヤホンボタンイベントを取得するコード

       @Override          public  int  onStartCommand(Intent  intent,  int  flags,  int  startId){                  AudioManager  am  =  (AudioManager)  this.getSystemService(Context.AUDIO_SERVICE);                  myEventReceiver  =  new  ComponentName(getPackageName(),  MyRemoteControlEventReceiver.class.getName());                  am.registerMediaButtonEventReceiver(myEventReceiver);                  return  START_NOT_STICKY;          }                  @Override          public  void  onDestroy()  {                  AudioManager  am  =  (AudioManager)  this.getSystemService(Context.AUDIO_SERVICE);                  am.unregisterMediaButtonEventReceiver(myEventReceiver);          }  

MainService.java

@Override public void onReceive(Context context, Intent intent) { // カメラ起動 Intent intent2 = new Intent(); //intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent2.setAction("android.media.action.IMAGE_CAPTURE"); startActivity(intent2); }

MyRemoteControlEventReceiver.java

Page 41: Android最速のフォトグラファーに、俺はなる!

さらに仕込む

<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>

AndroidManifest.xml

PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wakelock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "Your App Tag"); wakelock.acquire(); wakelock.release();

// フラグセット Window window = getWindow(); window.addFlags( WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);

スリープを解除するコード

画面ロックをパスするコード

ここまでROM改造なし、誰でもボタン入力を扱える

Page 42: Android最速のフォトグラファーに、俺はなる!

デモ※本日二回目

Page 43: Android最速のフォトグラファーに、俺はなる!

多摩川の直線鬼が シャッターチャンスに遭遇してから シャッター押下までのタイムは わずか1.2秒 (自分調べ) にすぎない

Page 44: Android最速のフォトグラファーに、俺はなる!

では、 その撮影プロセスを もう一度見てみよう

Page 45: Android最速のフォトグラファーに、俺はなる!
Page 46: Android最速のフォトグラファーに、俺はなる!

最速!

あらゆる状況で最速!!

Page 47: Android最速のフォトグラファーに、俺はなる!

友人 なんか似たようなん 既に見たで

Page 48: Android最速のフォトグラファーに、俺はなる!

Page 49: Android最速のフォトグラファーに、俺はなる!

友人

Page 50: Android最速のフォトグラファーに、俺はなる!
Page 51: Android最速のフォトグラファーに、俺はなる!

絶望が俺のゴールだ

Page 52: Android最速のフォトグラファーに、俺はなる!

発表は以上です ご静聴ありがとうございました

申し訳ありませんでした