본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Android/안드로이드] 프로를 위한 팁 ( ProTip ) - 부제 : Featured App 만들기

by 돼지왕 왕돼지 2012. 3. 7.
반응형

 
안녕하세요 돼지왕 왕돼지입니다.

오늘은 Android 프로를 위한 팁 ( ProTip ) 에 대한 Video Clip 을 보았습니다.
이 Video Clip의 내용은 결국 Featured 된 App 을 만들자는 것인데,
간단히 이야기하면 "좋은 앱"을 만드자는 내용이지요.


영어가 매우 빠르고, 강연자의 사투리때문에 알아든느데 쪼끔 어려운 점은 있지만, 내용은 매우 좋습니다.
어려워도, 머리 아파도 끝까지 한번쯤은 보신다면 다음번에 앱을 설계하실 때는
professional 하게 만들 수 있지 않을 까 생각합니다.




다양한 SDK 지원하기.

다양한 SDK 지원하기 Backward Compatibility ( 하위 호환성 ) 에 기인하며, 보통 new API 때문에 발생하곤 합니다.
예를 들어 HoneyComb에서부터 지원되는 ActionBar 를 사용하는 앱을 만든다면, 그 아래 버전인 GingerBread에서는 그것을 제대로 해석하지 못하고, minSDK 옵션때문에 아예 설치가 안 될 수 있습니다. 사실 어떤 앱을 만들 때 특정 SDK 를 타겟으로 만든다는 것은 그만큼 시장이 작아진다는 의미로 해석될 수 있기 때문에 좋지 않습니다.

Multi-Activity 만들기


private static boolean isOldAPIs = android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.GINGERBREAD;

@Override
public void onCreate( Bundle savedInstanceState ){
   Intent intent = null;  
   if ( isOldAPIs )
        intent = new Intent ( this, OldVersion.class );
   else
        intent = new Intent( this, NewVersion.class );
   startActivity( intent );


위의 방법이면, GingerBread 이하일 경우는 OldVersion 이라는 Activity 를 launch 하고, GingerBread 이상일 경우는 NewVersion Activity를 launch 하게 됩니다. 로직자체는 거의 비슷하고, View 부분만 바뀌기 때문에 MVC Model 을 충실히 따른 program 일 경우 수월하게 support 할 수 있을 것으로 보입니다.
 






xml 을 통한 지원


모두가 친숙할 것으로 여겨지는, layout folder 확장 방법입니다. 왜 이것이 Multi-SDK 를 지원하는 것이냐 하겠지만, 유심히 보시면, Phone 개발만 하셨던 분들에게 친숙하지 않은 xlarge 라는 옵션이 들어간 녀석이 있습니다. 이 녀석들은 주로 tablet 에서만 지원되는 녀석으로 honeycomb 의 경우 이 layout 을 따르게 되겠습니다. 이 방법으로도 multi-SDK support 를 성취할 수 있는 것이죠.

res/layout-port
res/layout-land
res/layout-xlarge-port-v11
res/layout-xlarge-land 
 


 


다양한 장비에 대해 지원하기.

다양한 장비 지원은 앱이 특정 하드웨어를 사용할 때 발생하는 문제입니다. 예를 들어 Accelerometer sensor 를 사용할 경우, 어떤 장비는 Gyroscrope 를 사용하고, 어떤 녀석은 단순 orientation sensor 를 사용하는 등의 문제가 있을 수 있습니다. 따라서, 특정 hardware 를 사용하는 경우에는 장비에 대한 검색이 선행되어야 합니다.

boolean isNewSensorAPIsSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE;
boolean gyroExists = getPackageManager().hasSystemFeature(
PackageManager.FEATURE_SENSOR_GYROSCOPE );

IOrientationSensorListener myListener; 
if ( gyroExists )
     myListener = new GyroOrientationSensorListener();
else if ( newSensorAPIsSupported ) 
     myListener = new AccOrientationSensorListener();
else
     myListener = new AccOldOrientationSensorListener(); 






Beta Test 하기.

Test 는 매우 중요합니다. 모든 개발자가 동의할 것입니다. 하지만, 개인 개발자의 경우 자신이 테스트 하는 데 한계가 있고, 모든 language, 모든 country, 모든 장비를 테스트하는 것은 불가능합니다. 따라서 Beta 버전으로 market에 release 를 하여 Test 를 하는 것이 매우 큰 도움이 됩니다.

Market 을 이용하여 Beta Test 를 할 때 다음과 같은 사항을 주의하여야 합니다.
- 비공개용일 경우 앱에 login, password 를 걸어 놓습니다.
- 공개용일지라도 Beta 테스터들만 활용할 수 있도록, market list 에 나오게 힘들게 합니다.
- 앱이 launch 될 때 Beta Version 임을 확실히 표시하여 User 에게 알려줍니다.
- Feedback 제공시의 혜택을 제공합니다.

[AB Test]

 








Market에서 package name & keystore 보호하기

PackageName 과 Keystore 보호하기가 무슨 말이냐구요?

PackageName 보호하기


Market 에 앱을 등록할 때, 다른 유저가 올린 앱의 PackageName과 내 앱의 PackageName이 같을 경우 upload 할 수 없습니다. 열심히 개발했는데 못 쓴다면 문제가 되겠죠? 게다가 특정 회사의 경우 해당 PackageName 을 변경하기 어려운 경우도 분명 있을것이구요. 이럴때는 먼저 아무 앱이나 만들어서 distribute 전에 upload 를 해놓습니다. ( publish 는 하지 않습니다. ) 이렇게 하면, 남들보다 먼저 package name  을 점유할 수 있는 것이지요.


Keystore 보호하기.


Keystore 가 무엇인지는 모두들 아실 것이라 생각합니다. App 을 Sign 하는 녀석인데, 똑같은 package name 을 가진 같은 앱을 다른 key로 sign 하여 올린다면, 실패합니다. 이제 해당 앱은 고아가 되어 버립니다. 더 이상 관리할 수 없는 녀석이 되어버리죠. 상상해 보세요. Keystore 를 잃어버려서 카카오톡이 더 이상 업데이트 되지 않고, 다른 package 이름으로 재설치가 되고, 기존의 것은 삭제가 되어야 하는 경우를.. 스마트폰을 사용만 할 줄 아는 사람의 경우 앱을 어떻게 삭제하는지도 모를수도 있고, 앱을 삭제하지 않고 새로운 package 의 동일한 앱을 다운받는다면, 충돌이 일어나기 쉽습니다. 
따라서 keystore 는 반드시 backup 을 잘 해놓아야 합니다.


마지막으로 여담으로 마켓 등록시의 이메일은 personal gmail 을 사용하지 않을 것을 권장하고 있습니다. 개발에 대한 feedback 들과 개인의 일은 구분되는 것이 좋기 때문이죠.
 





Orientation 을 잘 설정하자.

고급 안드로이드 개발자들은 모든 것이 "항상 이럴 것이다" 라고 가정 하는 것을 매우 위험하게 생각합니다. Orientation도 마찬가지입니다. Natural orientation 이 항상 portrait 라고 생각하는 것은 문제가 있습니다. Phone 의 경우는 portrait 일 경우가 높지만, Tablet 의 경우는 landscape가 대부분이겠죠. 따라서 Display.getRotation() 을 통해서 orientation 값을 얻어서 그에 맞는 설정이 필요합니다. Sensor 를 사용할 경우에 특히 이 start rotation은 critical 하죠.

int x = AXIS_X;
int y = AXIS_Y;

switch( Display.getRotation() ){
    case Surface.ROTATION_0 : break;
    case Surface.ROTATION_90 : x = AXIS_Y; y = AXIS_MINUS_X; break;
    case Surface.ROTATION_180 : x = AXIS_MINUS_Y; break;
    case Surface.ROTATION_270 : x = AXIS_MINUS_Y; y = AXIS_X; break;


SensorManager.remapCoordinateSystem( inR, x, y, outR ); 

 





특정 장비를 기억하라.

대부분의 스마트 디바이스들은 개인용으로 사용됩니다. 따라서 개인 정보를 sync 해놓곤 합니다. 예를 들면 email 이라던지, facebook account 라던지 말이죠. 마찬가지로, 어떤 앱에 대해 어떤 정보를 저장해 놓을 필요가 있습니다. 이 때 다음과 같이 UUID 를 사용하면, 특정 디바이스를 Preference 기억하여 동일한 result 를 주는 데 좋습니다.

private static String uniqueID = null;
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";

public synchronized static String id( Context context ){
   uniqueID = sp.getString( PREF_UNIQUE_ID, null );
   if ( uniqueID = null ){
      uniqueID = UUID.randomUUID().toString();
      Editor editor = sp.edit();
      editor.putString( PREF_UNIQUE_ID, uniqueID );
      editor.commit();
   }

return uniqueID; 

  




Android Blog 글을 마니 읽어라.

http://android-developers.blogspot.com 을 자주 참조하고 학습하라.








 

Fresh App 을 만들자.

Fresh 앱은 기다릴 필요가 없이 항상 최신의 데이터를 유지하는 것을 말한다. Fresh App 을 만드는 것과 Batter Consumption 은 반비례한다. 따라서 trade off 를 잘 고려하며, 적당한 선을 유지하는 것이 현명한 Fresh App 이라고 볼 수 있겠다.


Location

 
- Passive Location Provider 는 다른 App 이나 Service 가 요청했던 Location 정보를 받아낸다. 추가적 요청없이 최신의 내용을 유지하는 데 도움이 된다.

String passiveProvider = LocationManager.PASSIVE_PROVIDER;
locationManager.requestLocationUpdates( passiveProvider, minTime, minDistance, myLocationListener ); 

 

 - Intent 를 사용한 location change monitor 도 최신 정보를 유지하는 데 도움이 된다.

Intent intent = new Intent( locationAction );
PendingIntent pi = PendingIntent.getBroadcast( this, resultCode, intent, flags );
locationManager.requestLocationUpdates( provider, minTime, minDistance, pi ); 

 
- 가장 최근 GPS 위치를 query 할 수 있는 last known location 을 활용하면 좋다.

- Best provider 의 enability 를 확인해서, enable 순간에 최신정보를 얻어 놓는 것이 좋다. 


Data


- Alarm manager 를 사용하여 data 를 최신으로 유지시켜 놓는다. 이 때 refresh rate 는 상황에 따라 다르게 한다.

- 인터넷 연결 관련 update 는 WIFI 가 연결 되어있을 때 more update.
- 베터리가 연결되어 있을 때 more update.
- Dock 되어 있을 때 more update.
- 베터리 양이 적을때는 less update or suspend update.
- 인터넷 연결을 하지 않을 수 있다면, 가능한한 연결하지 않는 방향으로 update. 
- Car Dock 상태에서는 less update or suspend update.

 
- 상황에 알맞게 receiver 의 enability 를 조정하는 것이 좋다. 

ComponentName receiver = new ComponentName( this, myReceiver.class );
PackageName pm = getPackageManager();
pm.setComponentEnabledSetting( receiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP ); 


- Service 도 현명하게 일할 필요가 있다.
 모든 일은 asych 로 일하도록 하고, 할 일을 마치자마자 신속하게 죽어야 하며, 다시 service 를 가동할지 여부는 START_STICKY 의 return 여부로 control 한다. 


 



Psychic App 을 만들자.

Psychic App 은 User 가 모든 것을 말하지 않아도, Phone 이 알 수 있는 것 또는 기억하는 것을 활용하여 현명하게 대부분을 처리해주는 것을 말합니다. 따라서 대부분의 입력 받은 것을 기억해야 하고, Intent 등을 활용하여 data 를 공유해야 하죠.

- AccountManager 를 사용하여 account 를 관리합니다.

- Never Forget ( Backup Service )

- User log 와 Web Preference 를 Sync 시킵니다. ( Server 가 없다면, BackupManager 를 사용 )
- UUID 를 Preference 에 저장합니다. ( Anonymous user 를 위해 )
- Preference 는 device 를 바꿔도, reset 해도 유지되도록 해야 합니다.
- User 가 Preference 를 삭제 할 수 있도록 해야 합니다. 

 

@code
public class MyPrefsBackupAgent extends BackupAgentHelper{
   static final String PREFS = "user_preferences";
   static final String PREFS_BACKUP_KEY = "prefs";

   @override
   public void onCreate(){
      SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper( this, PREFS );
      addHelper( PREFS_BACKUP_KEY, helper );
   }


@manifest
<application android:label="MyApplication"
          android:backupAgent="MyPrefsBackupAgent">
          ......
         <meta-data android:name="com.google.android.backup.api_key"
                    android:value="MyAPIKey"/>
</application> 


 - intent 를 통해 data 를 share 한다.

@manifest
<intent-filter>
   ...
   <data android:scheme="http"
          android:host="mysite.com"
          android:pathPrefix="/news/articles/"/>
</intent-filter>  






Adaptive App 을 만들자.

Adaptive App 은 App 의 행동이 예상 가능해야 하며, 예상한대로 작동해야 하고, 다른 UX 환경에 optimize 되어 있어야 합니다. 따라서 특정 행동에 대해 User가 Notice 하게 해서는 안됩니다.

- EditText 의 InputType 옵션을 주어 알맞은 keyboard 띄우기.
 ( Capitalize, MultiLine, imeOption 등의 option 값을 제대로 준다. )

- Audio Focus 를 handle 하라.

int result = am.requestAudioFocus ( afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN );
if ( result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ){
   // start playback

am.abandonAudioFocus( afChangeListener );

OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener(){
   public void onAudioFocusChange( int focusChange ){
      if ( focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK ){
             // lower our volume ( ducking )
      }
      else if ( focusChange == AudioManager.AUDIOFOCUS_LOSS ){
            // pause playback
      }
   }

 
- Title과 Description 등은 반드시 multi-language 를 제공한다. 





Smooth App 을 만들자. 

Smooth App 은 User 가 기다리지 않아야 하며, 반응성이 좋고, 빠르고, 일관되어야 합니다. 가장 효율적으로 최적화되어 있어야 하는 것은 두말할 것은 없고, Smooth 느낌을 주기 위해 모든 것을 Animate 시켜주어야 하죠. 대부분의 활동은 Application thread 가 아닌, 다른 thread 에서 작동해야 합니다.

- 16ms delay 만 되어도 사람들은 버벅거림 ( jatter ) 를 느낄 수 있습니다. 

- 대부분의 작업은 다른 thread ( background thread ) 에서 처리해야 합니다. 

- Handler
- AsyncTask
- AsyncQueryHandler
- Loader and CursorLoader 


// From HoneyComb
getLoaderManager().initLoader( 0, null, this );

public Loader<Cursor> onCreateLoader( int id, Bundle args ){
   Uri baseUri = MyContentProvider.CONTENT_URI;
   return new CursorLoader( getActivity(), baseUri, null, null, null, null );


public void onLoadFinished( Loader<Cursor> loader, Cursor data ){
   mAdapter.swapCursor( data );


public void onLoaderReset( Loader<Cursor> loader ){
   mAdapter.swapCursor( null );

 
 - Strict Mode 를 사용하여 반응성을 미리 확인한다.

@Override
public void onCreate( Bundle savedInstances ){
   if ( DEVELOPER_MODE ){
      StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder().
                  .detectDiskReads()
                  .detectDiskWrites()
                  .detectNetwork()
                  .penaltyFlashScreen()
                  .build();
   }
   super.onCreate(); 

 


도움이 되셨다면 손가락 꾸욱~





반응형

댓글