[출처 : http://replygun.tistory.com/136]

ListView의 내용을 드래그 앤 드롭으로 순서를 변경하고 싶을 때 사용한다.

 

안드로이드 폰의 "Music" 앱의 playlist를 보면 아이템 드래그 앤 드롭이 가능하다는 것을 확인 할 수 있다. 다만 안드로이드 기본 API로 제공되지 않기 때문에 직접 구현해야만 한다. 드래그 앤 드롭이 적용된 ListView예제는 안드로이드 기본 앱인 Music에서도 확인 가능하며 필자가 만든 "마이투두" 앱에서도 확인이 가능하다.

 

다음은 안드로이드의 Music 앱의 소스를 참고하여 만든 DndListView이다.

아래의 코드를 사용하기 위해서는 안드로이드 버전 1.5 이상부터 가능하다.

 

  1. /*
    
     * Copyright (C) 2008 The Android Open Source Project
    
     *
    
     * Licensed under the Apache License, Version 2.0 (the "License");
    
     * you may not use this file except in compliance with the License.
    
     * You may obtain a copy of the License at
    
     *
    
     *      http://www.apache.org/licenses/LICENSE-2.0
    
     *
    
     * Unless required by applicable law or agreed to in writing, software
    
     * distributed under the License is distributed on an "AS IS" BASIS,
    
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    
     * See the License for the specific language governing permissions and
    
     * limitations under the License.
    
     */
    
    
    
    package org.pyframe.tools.view;
    
    
    
    import android.content.Context;
    
    import android.graphics.Bitmap;
    
    import android.graphics.PixelFormat;
    
    import android.graphics.Rect;
    
    import android.util.AttributeSet;
    
    import android.view.GestureDetector;
    
    import android.view.Gravity;
    
    import android.view.MotionEvent;
    
    import android.view.View;
    
    import android.view.ViewConfiguration;
    
    import android.view.ViewGroup;
    
    import android.view.WindowManager;
    
    import android.widget.AdapterView;
    
    import android.widget.ImageView;
    
    import android.widget.ListView;
    
    
    
    import com.mytodo.andriod.R;
    
    
    
    public class DndListView extends ListView {
    
        
    
    	private Context mContext;
    
        private ImageView mDragView;
    
        private WindowManager mWindowManager;
    
        private WindowManager.LayoutParams mWindowParams;
    
        private int mDragPos;      // which item is being dragged
    
        private int mFirstDragPos; // where was the dragged item originally
    
        private int mDragPoint;    // at what offset inside the item did the user grab it
    
        private int mCoordOffset;  // the difference between screen coordinates and coordinates in this view
    
        private DragListener mDragListener;
    
        private DropListener mDropListener;
    
    //    private RemoveListener mRemoveListener;
    
        private int mUpperBound;
    
        private int mLowerBound;
    
        private int mHeight;
    
        private GestureDetector mGestureDetector;
    
    //    private static final int FLING = 0;
    
    //    private static final int SLIDE = 1;
    
    //    private int mRemoveMode = -1;
    
        private Rect mTempRect = new Rect();
    
        private Bitmap mDragBitmap;
    
        private final int mTouchSlop;
    
        private int mItemHeightNormal;
    
        private int mItemHeightExpanded;
    
    
    
        public DndListView(Context context, AttributeSet attrs) {
    
            super(context, attrs);
    
    //        SharedPreferences pref = context.getSharedPreferences("Music", 3);
    
    //        mRemoveMode = pref.getInt("deletemode", -1);
    
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    
            mContext = context;
    
    //        Resources res = getResources();
    
    //        mItemHeightNormal = res.getDimensionPixelSize(R.dimen.normal_height);
    
    //        mItemHeightExpanded = res.getDimensionPixelSize(R.dimen.expanded_height);
    
        }
    
        
    
        @Override
    
        public boolean onInterceptTouchEvent(MotionEvent ev) {
    
            if (mDragListener != null || mDropListener != null) {
    
                switch (ev.getAction()) {
    
                    case MotionEvent.ACTION_DOWN:
    
                        int x = (int) ev.getX();
    
                        int y = (int) ev.getY();
    
                        int itemnum = pointToPosition(x, y);
    
                        if (itemnum == AdapterView.INVALID_POSITION) {
    
                            break;
    
                        }
    
                        ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());
    
                        mDragPoint = y - item.getTop();
    
                        mCoordOffset = ((int)ev.getRawY()) - y;
    
                        View dragger = item.findViewById(R.id.dragicon);
    
                        
    
    //                    item.setBackgroundColor(Color.RED);
    
                        
    
                        Rect r = mTempRect;
    
                        dragger.getDrawingRect(r);
    
                        // The dragger icon itself is quite small, so pretend the touch area is bigger
    
                        if (x < r.right * 2) {
    
                            item.setDrawingCacheEnabled(true);
    
                            // Create a copy of the drawing cache so that it does not get recycled
    
                            // by the framework when the list tries to clean up memory
    
                            Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache());
    
                            startDragging(bitmap, y);
    
                            mDragPos = itemnum;
    
                            mFirstDragPos = mDragPos;
    
                            mHeight = getHeight();
    
                            int touchSlop = mTouchSlop;
    
                            mUpperBound = Math.min(y - touchSlop, mHeight / 3);
    
                            mLowerBound = Math.max(y + touchSlop, mHeight * 2 /3);
    
                            return false;
    
                        }
    
                        mDragView = null;
    
                        break;
    
                }
    
            }
    
            return super.onInterceptTouchEvent(ev);
    
        }
    
        
    
        /*
    
         * pointToPosition() doesn't consider invisible views, but we
    
         * need to, so implement a slightly different version.
    
         */
    
        private int myPointToPosition(int x, int y) {
    
            Rect frame = mTempRect;
    
            final int count = getChildCount();
    
            for (int i = count - 1; i >= 0; i--) {
    
                final View child = getChildAt(i);
    
                child.getHitRect(frame);
    
                if (frame.contains(x, y)) {
    
                    return getFirstVisiblePosition() + i;
    
                }
    
            }
    
            return INVALID_POSITION;
    
        }
    
        
    
        private int getItemForPosition(int y) {
    
            int adjustedy = y - mDragPoint - 32;
    
            int pos = myPointToPosition(0, adjustedy);
    
            if (pos >= 0) {
    
                if (pos <= mFirstDragPos) {
    
                    pos += 1;
    
                }
    
            } else if (adjustedy < 0) {
    
                pos = 0;
    
            }
    
            return pos;
    
        }
    
        
    
        private void adjustScrollBounds(int y) {
    
            if (y >= mHeight / 3) {
    
                mUpperBound = mHeight / 3;
    
            }
    
            if (y <= mHeight * 2 / 3) {
    
                mLowerBound = mHeight * 2 / 3;
    
            }
    
        }
    
    
    
        /*
    
         * Restore size and visibility for all listitems
    
         */
    
        private void unExpandViews(boolean deletion) {
    
            for (int i = 0;; i++) {
    
                View v = getChildAt(i);
    
                if (v == null) {
    
                    if (deletion) {
    
                        // HACK force update of mItemCount
    
                        int position = getFirstVisiblePosition();
    
                        int y = getChildAt(0).getTop();
    
                        setAdapter(getAdapter());
    
                        setSelectionFromTop(position, y);
    
                        // end hack
    
                    }
    
                    layoutChildren(); // force children to be recreated where needed
    
                    v = getChildAt(i);
    
                    if (v == null) {
    
                        break;
    
                    }
    
                }
    
                ViewGroup.LayoutParams params = v.getLayoutParams();
    
                params.height = mItemHeightNormal;
    
                v.setLayoutParams(params);
    
                v.setVisibility(View.VISIBLE);
    
            }
    
        }
    
        
    
        /* Adjust visibility and size to make it appear as though
    
         * an item is being dragged around and other items are making
    
         * room for it:
    
         * If dropping the item would result in it still being in the
    
         * same place, then make the dragged listitem's size normal,
    
         * but make the item invisible.
    
         * Otherwise, if the dragged listitem is still on screen, make
    
         * it as small as possible and expand the item below the insert
    
         * point.
    
         * If the dragged item is not on screen, only expand the item
    
         * below the current insertpoint.
    
         */
    
        private void doExpansion() {
    
            int childnum = mDragPos - getFirstVisiblePosition();
    
            if (mDragPos > mFirstDragPos) {
    
                childnum++;
    
            }
    
    
    
            View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());
    
    
    
            for (int i = 0;; i++) {
    
                View vv = getChildAt(i);
    
                if (vv == null) {
    
                    break;
    
                }
    
                int height = mItemHeightNormal;
    
                int visibility = View.VISIBLE;
    
                if (vv.equals(first)) {
    
                    // processing the item that is being dragged
    
                    if (mDragPos == mFirstDragPos) {
    
                        // hovering over the original location
    
                        visibility = View.INVISIBLE;
    
                    } else {
    
                        // not hovering over it
    
                        height = 1;
    
                    }
    
                } else if (i == childnum) {
    
                    if (mDragPos < getCount() - 1) {
    
                        height = mItemHeightExpanded;
    
                    }
    
                }
    
                ViewGroup.LayoutParams params = vv.getLayoutParams();
    
                params.height = height;
    
                vv.setLayoutParams(params);
    
                vv.setVisibility(visibility);
    
            }
    
        }
    
        
    
        @Override
    
        public boolean onTouchEvent(MotionEvent ev) {
    
            if (mGestureDetector != null) {
    
                mGestureDetector.onTouchEvent(ev);
    
            }
    
            if ((mDragListener != null || mDropListener != null) && mDragView != null) {
    
                int action = ev.getAction(); 
    
                switch (action) {
    
                    case MotionEvent.ACTION_UP:
    
                    case MotionEvent.ACTION_CANCEL:
    
                        Rect r = mTempRect;
    
                        mDragView.getDrawingRect(r);
    
                        stopDragging();
    
    //                    if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) {
    
    //                        if (mRemoveListener != null) {
    
    //                            mRemoveListener.remove(mFirstDragPos);
    
    //                        }
    
    //                        unExpandViews(true);
    
    //                    } else {
    
                        if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) {
    
                            mDropListener.drop(mFirstDragPos, mDragPos);
    
                        }
    
                        unExpandViews(false);
    
    //                    }
    
                        break;
    
                        
    
                    case MotionEvent.ACTION_DOWN:
    
                    case MotionEvent.ACTION_MOVE:
    
                        int x = (int) ev.getX();
    
                        int y = (int) ev.getY();
    
                        dragView(x, y);
    
                        int itemnum = getItemForPosition(y);
    
                        if (itemnum >= 0) {
    
                            if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) {
    
                                if (mDragListener != null) {
    
                                    mDragListener.drag(mDragPos, itemnum);
    
                                }
    
                                mDragPos = itemnum;
    
                                doExpansion();
    
                            }
    
                            int speed = 0;
    
                            adjustScrollBounds(y);
    
                            if (y > mLowerBound) {
    
                                // scroll the list up a bit
    
                                speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;
    
                            } else if (y < mUpperBound) {
    
                                // scroll the list down a bit
    
                                speed = y < mUpperBound / 2 ? -16 : -4;
    
                            }
    
                            if (speed != 0) {
    
                                int ref = pointToPosition(0, mHeight / 2);
    
                                if (ref == AdapterView.INVALID_POSITION) {
    
                                    //we hit a divider or an invisible view, check somewhere else
    
                                    ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64);
    
                                }
    
                                View v = getChildAt(ref - getFirstVisiblePosition());
    
                                if (v!= null) {
    
                                    int pos = v.getTop();
    
                                    setSelectionFromTop(ref, pos - speed);
    
                                }
    
                            }
    
                        }
    
                        break;
    
                }
    
                return true;
    
            }
    
            return super.onTouchEvent(ev);
    
        }
    
        
    
        private void startDragging(Bitmap bm, int y) {
    
            stopDragging();
    
    
    
            mWindowParams = new WindowManager.LayoutParams();
    
            mWindowParams.gravity = Gravity.TOP;
    
            mWindowParams.x = 0;
    
            mWindowParams.y = y - mDragPoint + mCoordOffset;
    
    //
    
            mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    
            mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    
            mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
    
                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
    
            mWindowParams.format = PixelFormat.TRANSLUCENT;
    
            mWindowParams.windowAnimations = 0;
    
            
    
            ImageView v = new ImageView(mContext);
    
            int backGroundColor = mContext.getResources().getColor(R.color.dragndrop_background);
    
    //        int backGroundColor = Color.parseColor("#e0103010");
    
            v.setBackgroundColor(backGroundColor);
    
            
    
            v.setImageBitmap(bm);
    
            mDragBitmap = bm;
    
    
    
            mWindowManager = (WindowManager)mContext.getSystemService("window");
    
            mWindowManager.addView(v, mWindowParams);
    
            mDragView = v;
    
        }
    
        
    
        private void dragView(int x, int y) {
    
    //        if (mRemoveMode == SLIDE) {
    
    //            float alpha = 1.0f;
    
    //            int width = mDragView.getWidth();
    
    //            if (x > width / 2) {
    
    //                alpha = ((float)(width - x)) / (width / 2);
    
    //            }
    
    //            mWindowParams.alpha = alpha;
    
    //        }
    
            mWindowParams.y = y - mDragPoint + mCoordOffset;
    
            mWindowManager.updateViewLayout(mDragView, mWindowParams);
    
        }
    
        
    
        private void stopDragging() {
    
            if (mDragView != null) {
    
                WindowManager wm = (WindowManager)mContext.getSystemService("window");
    
                wm.removeView(mDragView);
    
                mDragView.setImageDrawable(null);
    
                mDragView = null;
    
            }
    
            if (mDragBitmap != null) {
    
                mDragBitmap.recycle();
    
                mDragBitmap = null;
    
            }
    
        }
    
        
    
        public void setDragListener(DragListener l) {
    
            mDragListener = l;
    
        }
    
        
    
        public void setDropListener(DropListener l) {
    
            mDropListener = l;
    
        }
    
        
    
    //    public void setRemoveListener(RemoveListener l) {
    
    //        mRemoveListener = l;
    
    //    }
    
    
    
        public interface DragListener {
    
            void drag(int from, int to);
    
        }
    
        public interface DropListener {
    
            void drop(int from, int to);
    
        }
    
        public interface RemoveListener {
    
            void remove(int which);
    
        }
    
    }

 

위 코드중 정상적인 컴파일을 위해서 살펴보아야 할 부분이 두군데 있다.

 

1. 위 코드에 다음과 같은 부분이 있다.

  1. View dragger = item.findViewById(R.id.dragicon);

 

R.id.dragicon 이 바로 드래그를 할 대상이 되는 View가 된다.

 

2. 또 다음과 같은 코드가 있다.

  1. int backGroundColor = mContext.getResources().getColor(R.color.dragndrop_background);

 

이 부분은 드래그 앤 드롭시 백그라운드 색상을 지정해 주는 부분이다.

 

strings.xml파일에 다음과 같은 항목을 추가해 주어야 한다.

  1. <color name="dragndrop_background">#e0103010</color>

 

 

이제 드래그 앤 드롭을 구현하기 위해서 ListActivity는 어떻게 구현해야 하는지 알아보자.

 

아래와 같이 DragListener와 DropListener를 구현하도록 ListActivity를 만든다.

  1. public class MainActivity extends ListActivity implements DragListener, DropListener {

 

onCreate메써드에서 드래그 앤 드롭을 사용한다는 정보를 입력한다.

  1. listView = (DndListView) findViewById(android.R.id.list);
  2. listView.setDragListener(this);
    listView.setDropListener(this);

 

그리고 다음과 같은 메써드를 구현한다.

  1. public void drag(int from, int to) {
  2.   // 드래그 이벤트가 발생시 구현해야 할 것들을 기술한다.
    }
  3.  
  4. public void drop(int fr, int to) {
  5.   // 드롭 이벤트 발생시 구현해야 할 것들을 기술한다.
  6. }

 

Activity의 레이아웃 파일은 다음과 같이 작성해야 한다.

  1.     <org.pyframe.tools.view.DndListView
            android:id="@android:id/list"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:drawSelectorOnTop="false"
            android:fastScrollEnabled="true"
            android:cacheColorHint="#00000000"
            android:layout_weight="1.0"
             />
 

cacheColorHint값을 주어야 드래그 앤 드롭시 선택된 아이템의 백그라운드 색상이 표시된다.

Posted by dlucky
[출처 : http://mainia.tistory.com/549]

안드로이드(Android) 단말기에서 ConnectivityManager 를 통해 실시간 네트웍 상태 파악하기

 

개발환경 : JDK 1.5, Android GoogleAPI 2.1 , eclipse-galileo, window XP

 

이 예제는 단말기에서 WIFI, 3G 가 되는지에 대한 체크를 위한 것이다.

두가지 정보를 체크할것이다하나는 네트웍을 사용할 시점에 WIFI, 3G 

사용가능한지에 대한 체크이며 다른 하나는 네트웍상태가 바뀌었을 때

상태변화에 따른 작업을 하기 위한 소스 작업이다.


(1) 네트웍을 사용할 시점에서의 체크

 

먼저 permission 을 설정한다네트웍을 사용하기 위한 permission 은 아래와같다.

이 내용을 AndroidManifest.xml 에 추가한다. INTERNET 만 추가하면 안된다.

ACCESS_NETWORK_STATE 도 추가하기 바란다.


1 <uses-permission android:name="android.permission.INTERNET">
2 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">
3 </uses-permission></uses-permission>

다음은 ConnectivityManager 객체를 통해 WIFI  3G 상태를 체크할수 있는

객체를 반환해서 상태를 체크한다
1 ConnectivityManager manager =
2     (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

WIFI, 3G 인지 구분없이 네트웍 연결상태가 제대로 되었는지에 대한 소스는

아래와 같다. isConnectionted 함수를 써서 확인한다.
01 ConnectivityManager manager =
02    (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
03 NetworkInfo mobile = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
04 NetworkInfo wifi = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
05          
06 if (mobile.isConnected() || wifi.isConnected()){
07       // WIFI, 3G 어느곳에도 연결되지 않았을때
08       Log.d(TAG, "Network connect success");
09 }else{
10       Log.d(TAG, "Network connect fail");
11 }

현재 위의 소스를 돌여서 나온 MOBILE WIFI 의 상태값에 대한 내용이다.

인터넷선이 연결된 컴퓨터에서 테스트 했으니 당연 MOBILE 만 될것이다.

MOBILE(3G) 디버그 상태값

WIFI 상태 디버그 상태값

그럼 MOBILE 과 WIFI 둘중 어떤것에 연결되었는지에 대한 상태에 따라 구현 소스가

틀려진다면 어떻게 해야할까그것은 ConnectivityManager  getActiveNetworkInfo() 

NetworkInfo  객체를 받은후 getTypeName 으로 체크하면 된다리턴값으로 String 

반환하는데 “MOBILE”, “WIFI” 둘중 하나를 리턴한다.

1 NetworkInfo ni = manager.getActiveNetworkInfo();
2 String netname = ni.getTypeName();
3 if (netname.equals("MOBILE")) {
4     Log.d(TAG, "Network - > " + netname);
5 }else{
6     Log.d(TAG, "Network - > " + netname);
7 }

(2) 실시간 네트웍 상태 변화에 따른 값받기

 

android.content.BroadcastReceiver 클래스를 상속받아 하나 만든다.

01 import android.content.BroadcastReceiver;
02 import android.content.Context;
03 import android.content.Intent;
04 import android.net.ConnectivityManager;
05 import android.net.NetworkInfo;
06 import android.widget.Toast;
07  
08 public class ConnReceiver extends BroadcastReceiver {
09  
10     @Override
11     public void onReceive(Context context, Intent intent) {
12         String action = intent.getAction();
13          
14         // 네트웍에 변경이 일어났을때 발생하는 부분
15         if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
16             ConnectivityManager connectivityManager =
17                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
18             NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
19             NetworkInfo mobNetInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
20             Toast.makeText(context,"Active Network Type : " + activeNetInfo.getTypeName() , Toast.LENGTH_SHORT).show();
21             Toast.makeText(context,"Mobile Network Type : " + mobNetInfo.getTypeName() , Toast.LENGTH_SHORT).show();
22         }
23     }
24 }

다음은 AndroidManifest.xml 에 recevier 와 필터를 등록한다필터는

android.net.conn.CONNECTIVITY_CHANGE  등록하면 된다그리고 receiver에는

상속받아 만든 클래스를 넣는다.

Posted by dlucky
다이얼로그의 재사용성을 위해 onCreateDialog를 사용하면 설정해둔 리스트를 업뎃을 못해
난관에 봉착할 때가 있다. (바꿀수도 없고 참;;;;)

그럴 때는 onPrepareDialog에서 다시 그려야할 다이얼로그가 있을 때
removeDialog(id)를 하니까 다시 새로 그린다.

    @Override    protected void onPrepareDialog(int id, Dialog dialog){
     //해당 다이얼로그는 매번 다시 부름. 
    switch(id){
      //없에면 다시 새로 그림
      removeDialog(id);
    }}
Posted by dlucky

[출처: http://www.bloter.net/archives/43970 ]
안드로이드 2.3 진저브레드가 발표되었습니다. 새로운 기능을 정리하고 약간의 부연 설명을 곁들였습니다.

인터넷전화 (mVoIP)

진저브레드부터 안드로이드에 인터넷 전화 프로토콜이 기본으로 탑재됩니다. 사용자는 전화번호부에서 바로 SIP 계정을 가진 인터넷 전화로 전화를 걸수 있습니다. 인터넷 전화를 걸기 위해서는 SIP 계정을 따로 만들어야 하며 SIP 계정을 만드는 기능은 포함되지 않았습니다.

진저브레드에서는 따로 인터넷 전화용 앱을 깔 필요없이 기존 전화번호부에 통합되어, 인터넷 전화로 전화 걸기가 일반 전화를 거는것과 동일한 사용자 경험을 제공합니다. 이는 차츰 퍼져가고 있던 스마트폰에서의 인터넷 전화의 확산에 기름을 붇는 것입니다. 인터넷 전화 기능은 제조사나 통신사에 따라 포함되지 않을수도 있습니다. 하지만 안드로이드의 기본 기능으로 들어간 이상 통신사들도 mVoIP에 적극 대비해야 하며 인터넷 전화 회사들은 시장 확대에 따른 성장이 기대됩니다.

근거리 무선 통신 (NFC)

진저브레드에서는 NFC를 지원하여 해당 하드웨어가 탭재된 단말에서는 모바일 결제, RFID 태그 정보 습득이 가능해 집니다. NFC의 탑재로 스마트폰이 기존 신용 카드, 교통 카드의 역할을 대체할 수 있으며 양방향 통신으로 결제를 받을 수도 있어 다양한 형태의 결제가 가능하게 됩니다. RFID 태그를 읽을 수 있어 상점들의 포스터, 스티커, 광고에 붙어 있는 이벤트 관련 태그 정보를 읽어 해당 이벤트를 웹브라우저로 열수 있게 됩니다.

NFC 하드웨어를 탑재한 폰이 출시되고 있지만 여전히 상용화나 서비스 활성화가 쉽지 않은 상황에서 안드로이드에서 NFC를 공식적으로 지원하고 레퍼런스폰인 넥서스S에 탑재함으로써 시장 확대가 급물살을 타게 되었습니다.

게임 개발 기능 강화

기존 안드로이드의 게임은 다른 플랫폼에 비해 상대적으로 약합니다. 이번 진저브레드에서는 게임 개발의 향상을 위해 시스템 전체적으로 많은 변경이 있었습니다. 게임중 종종 화면의 멈춤 현상을 일으키는 가비지 컬렉션의 문제를 해결하기 위해 동시 가비지 컬렉터 (Concurrent Garbase Collector) 기술을 채택하였으며 화면 터치와 키보드 입력을 효율적으로 처리해 반응 속도를 빠르게하고 3D 비디오 드라이버를 업데이트해 전체적인 3D 그래픽 성능도 향상됩니다.

안드로이드는 일반적으로 자바(Java)언어로 앱이 개발됩니다만 성능 향상을 위해 NDK라는 도구를 통해 프로그램 일부를 Native C/C++로 작성가능합니다. 게임에서는 최적화가 중요하기 때문에 성능 향상을 이끌어내기 위해 C/C++를 많이 사용하게 됩니다. 기존에는 NDK로 접근할 수 있는 영역이 많지 않았는데 진저브레드부터 대폭 확대되어 입력, 센서, 오디오, 그래픽 관리 등을 C/C++로 직접 접근할수 있어 게임 개발에서 시스템의 성능을 최대한 이끌어낼 수 있게 되었습니다.

기존에 탑재하고 있던 OpenGL ES, OpenMAX IL에 이어 OpenSL ES, EGL 등의 Khronos의 오픈 스탠다드를 추가적으로 Native 영역에서 채택함으로써 앞으로 멀티미디어 관련 하드웨어 분야의 발전을 통한 안드로이드의 시스템 성능 향상도 기대할수 있게 되었습니다.

복잡한 기술적인 용어들은 뒤로하고 사용자 입장에서 보면 기존에 보지 못한 강력한 성능의 게임을 안드로이드에서도 할수 있게 되었다는 것을 의미합니다.

전면 카메라 지원

애플리케이션이 후면 카메라뿐 아니라 전면 카메라로도 직접 접근할수 있게 되었습니다.  이것은 비디오 채팅을 포함한 다양한 비디오 활용 앱과 서비스가 등장할 수 있다는 것을 의미합니다.

2년전 전면 카메라를 활용한 동영상 SNS로 구글 안드로이드 개발자 대회 1차를 통과했지만 2차에서 갑자기 동영상 녹화 기능이 사라졌고, 폰 출시 이후 업데이트를 통해 동영상 녹화 기능이 지원되었지만 여전히 전면 카메라를 공식적으로 지원하지 않아 해당 서비스 개발을 포기했는데 이제야 전면 카메라를 통한 동영상 녹화가 가능해져 감회가 새롭습니다.

스마트폰의 사용과 앱의 개발에 큰 영향을 줄 수 있는 새로운 기능은 위와 같습니다. 기타 변경사항과 사용자 UI의 변경에 대해서도 좀더 살펴보겠습니다.

새로운 스크린 크기와 밀도 지원

  • 엑스트라 라지 스크린 지원 : 태블릿등 화면이 큰 장치를 지원합니다.
  • 엑스트라 하이 덴서티 지원 : 아이폰4 레티나 같은 수준의 높은 밀도의 스크린 장치를 지원합니다.

향상된 멀티미디어

구글이 공개한 로열티없는 미디어 포맷인 WebM과 VP8 비디오 코덱이 안드로이드에 탑재 되어 WebM의 확산에 도움을 주게 되었습니다. AAC와 AMR WB로의 음성 인코딩을 지원해 더 깨끗한 음질의 오디오 녹음이 가능해 졌습니다. 또한 이퀄라이저, 베이스 부스트, 헤드폰 버츄얼라이제이션등 다양한 오디오 효과의 적용이 가능해져 풍부한 사운드를 즐길수 있게 되었습니다.

UI의 개선

UI는 전체 시스템에 걸쳐 쉽고 빠르게 배울수 있도록 개선되었습니다. 검은색을 채택하여 알림바와 메뉴가 좀더 선명하게 드러날 수 있도록 했으며 배터리 효율성을 강화하였습니다. 메뉴와 설정도 좀더 쉽게 장치를 다룰수 있게 변경이 되었습니다.

기존 안드로이드는 왼쪽과 같이 밝은 회색이 기본 색상이었는데 진저브레드에서는 검은색을 채택하고 전체적으로 명도를 낮춰 어두운 색상에서 전력을 적게 소모하는 AMOLED에도 적합하게 변경되었습니다.

빠른 텍스트 입력

안드로이드 스크린 키보드는 더 빠른 입력과 편집을 할수 있도록 다시 설계되었습니다. 빠른 속도로 정확하게 입력할수 있도록 모양과 위치를 변경하였고 자동 완성을위한 글자도 더 커지고 선명하게 바뀌어 읽기 좋습니다. 입력된 단어를 선택하면 해당 단어를 대체할수 있는 단어를 사전에서 찾아 제안해 오타를 쉽게 수정할 수 있습니다. 스크린 키보드에서 멀티 터치를 지원해 Shift를 누른상태로 바로 숫자를 입력할수 있습니다.


간편해진 선택, 복사, 붙여넣기 기능

문자의 입력이나 웹브라우징시에 한번에 선택, 복사, 붙여넣기가 가능해 졌습니다. 단어를 터치하면 자유 선택모드로 들어가 영역 화살표를 드래깅하여 쉽게 선택 영역의 변경이 가능합니다. 이후 선택 영역의 터치로 복사가 가능해 졌고 텍스트 입력시 커서 모드에서 영역 화살표를 드래그하여 쉽게 영역 변경이 가능하여 영역 선택에 있어 트랙볼이 굳이 필요하지 않습니다.

향상된 배터리 관리 기능

시스템이 백그라운드에서 너무 오래 실행되어 전력을 많이 소모하는 앱들의 관리를 강화하여 성능을 향상하고 배터리 사용 시간을 늘립니다. 설정에서 사용자에게 시스템 콤포넌트와 앱들에 의해 소모되는 전력을 자세히 보여줍니다. 어떤 컴포넌트와 앱이 얼마나 전력을 소모했는지 상세히 보여줍니다.

애플리케이션 관리

애플리케이션 관리 설정이 홈 스크린의 옵션 메뉴에 들어가서 애플리케이션 관리와 검사를 더 빠르게 접근할 수 있게 되었습니다. 애플리케이션 관리 화면은 탭을 사용하여 구성되며 앱이 저장된 공간과 메모리 사용량을 쉽게 확인할 수 있습니다. 폰 사용자는 각 앱에 대한 자세한 정보를 확인하고 실행을 중지할수 있으며 개발자에게 피드백을 보낼 수 있습니다.

2.2프로요에서 2.3 진저브레드로 버전이 업그레이드 되면서 0.1이라는 적은 숫자가 올라갔고 플랫폼 자체가 직접 사용자에게 주는 기능은 커보이지 않습니다. 하지만 개발자에게 주는 의미는 2.1 에서 2.2로 업그레이드된것보다 훨씬 큽니다. 개발자들이 SIP, NFC, 게임 그리고 전면 카메라를 활용하여 얼마나 새로운 애플리케이션들을 만들어 낼지 또 그것이 사용자들에게 얼마나 큰 가치를 만들어낼지 많은 기대가 됩니다.

Posted by dlucky

<출처 : http://drcarter.tistory.com/entry/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B0%9C%EB%B0%9C-%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD-%EB%B0%B1%EA%B7%B8%EB%9D%BC%EC%9A%B4%EB%93%9C-%EC%8B%A4%ED%96%89%EC%8B%9C-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90>

안드로이드의 특징중에 하나를 꼽는다면 백그라운드 실행이라고 할 수 있겠습니다. 아이폰과 다르게 멀티테스킹이 가능한
안드로이드는 그만큼 활용도가 많다는 것입니다. 하지만 그것에 따른 다른 주의 사항이 있습니다.
그 중에 하나가 바로 wifi모드를 유지하는 것입니다.

  안드로이드는 대기 모드로 들어가게 되면 배터리 소모를 줄이기 위해서 wifi를 자동으로 꺼버리게 됩니다. 하지만 무선으로
데이터를 주고 받는 중이거나, 스티리밍을 하고 있는 중간에 대기 상태로 들어가게 되고 자동으로 wifi가 꺼지게 된다면 3G
의 무선 데이터 모드로 바뀌게 되겠지요. 그렇게 된다면 3G 데이터의 사용이 많아지게 되고 나중엔 사용자의 요금에 지대한
영향을 줄 것으로 생각됩니다.
  백그라운드 실행 중에도 wifi 상태를 유지하기 위해서는 

android.net.wifi.WifiManager.WifiLock

을 이용해서 wifi상태를 유지시켜줘야 합니다.
자세한 설명음
http://developer.android.com/reference/android/net/wifi/WifiManager.WifiLock.html
이 곳에 가시면 확인할 수 있습니다.

간단히 사용방법을 보게 된다면
WifiManager.WifiLock wifiLock = null;
//등록
if (wifiLock == null) {
                WifiManager wifiManager = (WifiManager) context.getSystemService(context.WIFI_SERVICE);
	wifiLock = wifiManager.createWifiLock("wifilock");
	wifiLock.setReferenceCounted(true);
	wifiLock.acquire();
}
//해제
if (wifiLock != null) {
                wifiLock.release();
	wifiLock = null;
}

이렇게 하면 됩니다.
대기 상태에서도 wifi를 유지하고 있게 된다면, 배터리 소모가 빠르고 많아진다는 단점이 있지만, 그만큼 3G 데이터를 사용하는것이 적어진다는 것에 대한 장점도 있게 됩니다.

  두번째로 주의할 점은, 대기상태가 오래 된다면 cpu의 활동을 정지시켜 버립니다. 이것도 배터리 소모를 줄이기 위한 것이기는 하지만, 단적인 예로 들어서 스트리밍으로 음악을 듣고 있는 중에서 화면을 꺼버린 대기 모드일 경우에 cpu를 정지 시킨다면 음악을 들을 수 없게 되겠지요. 그래서 wifi상태를 유지 시켜주기 위한 WifiLock이 있듯이 cpu상태를 활동상태로 유지시켜주는 WakeLock이 있습니다.

android.os.PowerManager.WakeLock

자세한 설명은
http://developer.android.com/reference/android/os/PowerManager.WakeLock.html
이곳에 가셔서 확인해 볼 수 있습니다.

이것을 사용하는 방법은 간단히
PowerManager.WakeLock wakeLock = null;
//등록
if (wakeLock == null) {
	PowerManager powerManager = (PowerManager) context.getSystemService(context.POWER_SERVICE);
	wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "wakelock");
	wakeLock.acquire();
}
//해제
if (wakeLock != null) {
	wakeLock.release();
	wakeLock = null;
}

이렇게 하면 됩니다.

이 두가지를 유지시켜 준다면, 백그라운드로 실행을 하면서 대기 모드로 전환이 된다고 해도, 해당 어플이 죽거나 하는 일은 없겠지만, 그만큼 배터리 소모가 많아지기 때문에 안드로이드폰을 오래 사용할 수는 없겠지요. 
Posted by dlucky
AudioManager aManager; 
aManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
    
    int mode = aManager.getRingerMode(); // 현재 모드 받아오기
    int nCurrentVolumn = aManager.getStreamVolume(AudioManager.STREAM_RING); // 현재 벨소리 크기 받아오기

    seekBar.setMax(maxValue); // 현재 프로그레스 바 최대 값 설정
    seekBar.setProgress(nCurrentVolumn); // 현재 프로그레스 바 값 설정
    seekBar.setOnSeekBarChangeListener(this);
    setCurrentValue(nCurrentVolumn);  

벨소리 크기를 설정하기 위해서는
    aManager.setStreamVolume(AudioManager.STREAM_RING, seekBar.getProgress(),    AudioManager.FLAG_PLAY_SOUND);


Posted by dlucky
AudioManager aManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
aManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); => 벨소리 모드
aManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE); =>진동 모드
aManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); => 무음 모드

이런 식으로 사용 해주면 됩니다.



Posted by dlucky
smsCallReceiver = new SmsCallReceiver(this);
registerReceiver(smsCallReceiver, new IntentFilter("android.provider.Telephony.SMS_RECEIVED"));

registerReceiver를 사용하게 되면 반드시 unregisterReceiver 함수를 사용해야 메모리 누수가 생기지 않는다.

보통은 onResume에서 registerReceiver를 사용하고 onPause에서 unregisterReceiver를 사용한다.


Posted by dlucky

<출처 : http://samse.tistory.com/tag/Activity%20context>


Android app들은 적어도 T-Mobile G1에서 16MB heap size제약이 있다. 이것은 phone을 위해서는 많은 양이면서 개발자에게는 아주 작은양이다. 대부분의 메모리를 사용할 생각이 아니더라도 다른 app들을 죽이지 않을 정도로 가능한 아주 작은 양의 메모리를 사용해야 한다. 더 많은 app들이 메모리에 유지되고 그들의 app들간에 전환이 일어날것이다. 내작업의 일부로써 메모리누수이슈가 속출할것이며 대부분의 시간을 이런 실수(하나의 Context에 오랬동안 레퍼런스를 유지하는것)때문에 보내게 될것이다. 

Android에서 Context는 resource를 load하고 access하는것을 제외한 많은 작업에 이용된다. 이것은 모든 widget
들이 생성자에서 Context 파라메터를 인자로 받는 이유이기도 하다. 일반적인 Android app에서 Activity와 
Application이라는 2개의 Context를 가진다. 이것은 보통 class와 method에 전달하는 첫번째 파라메터이다.

@Override
protected void onCreate(Bundle state) {
 
super.onCreate(state);
  
 
TextView label= new TextView(this);
  label
.setText("Leaks are bad");
  
  setContentView
(label);
}

위코드는 view가 전체액티비티에 하나의 레퍼런스를 가지고 있고 액티비티의 모든게 그안에 유지된다는것을 의미한다(모든 뷰구조와 그 리소스들), 그러므로 만일 Context가 누수(누수는 레퍼런스를 하나 유지하여 GC를 방지하는것을 의미한다)가 발생하면 당신은 많은 양의 메모리를 잃게된다. 전체 액티비티를 읽는것은 당신이 주의하지 않으면 아주 쉽게 일어날수 있다.

접기

This means that views have a reference to the entire activity and therefore to anything your activity is holding onto; usually the entire View hierarchy and all its resources. Therefore, if you leak the Context ("leak" meaning you keep a reference to it thus preventing the GC from collecting it), you leak a lot of memory. Leaking an entire activity can be really easy if you're not careful.

접기

디폴트로 화면이 전환되면 현재 액티비티는 제거되고 그 상태가 저장된 상태로 새로운 액티비티가 다시 생성된다. 이런 방식으로 안드로이드는 리소스들을 다시 로드하여 UI를 다시 구성한다. 당신이 로테이션시마다 다시 로드되지 않기를 원하는 아주 큰 비트맵을 사용하는 어플리케이션을 작성한다고 상상해보라.  아주 쉬운 방법은 static field를 사용하는 것이다.

private static Drawable sBackground;
 
@Override
protected void onCreate(Bundle state) {
 
super.onCreate(state);
  
 
TextView label= new TextView(this);
  label
.setText("Leaks are bad");
  
 
if (sBackground== null) {
    sBackground
= getDrawable(R.drawable.large_bitmap);
 
}
  label
.setBackgroundDrawable(sBackground);
  
  setContentView
(label);
}

이 코드는 아주 빠르지만 또한 아주 잘못된 코드이다. 처음 액티비티가 생성되어 처음 로테이션될때 leak이 발생된다. Drawable이 뷰에 첨부될때 view는 drawable에 callback으로 설정된다. 위 코드상에서 drawable은 TextView의 참조를 가지고 있다. TextView는 액티비티의 참조를 가지고 있다. 

접기

This code is very fast and also very wrong; it leaks the first activity created upon the first screen orientation change. When a Drawable is attached to a view, the view is set as a callback on the drawable. In the code snippet above, this means the drawable has a reference to the TextView which itself has a reference to the activity (the Context) which in turns has references to pretty much anything (depending on your code.)

접기

이 예제는 Context의 누수의 아주 단순한 케이스중 하나이며 Home screen소스상에서(unbindDrawables()메소드를 찾아보라) 저장된 drawable의 callback에서 액티비티가 제거될때 null로 초기화하는 것을 볼수 있다.  충분히 흥미진진하고 누수된 context의 연결고리를 생성할수 있는 여러 군데가 있으며 그것은 좋지 않은 방법이다. 그것들은 당신을 오히려 더 빠르게 메모리부족을 야기시킨다.

접기

This example is one of the simplest cases of leaking the Context and you can see how we worked around it in the Home screen's source code (look for the unbindDrawables() method) by setting the stored drawables' callbacks to null when the activity is destroyed. Interestingly enough, there are cases where you can create a chain of leaked contexts, and they are bad. They make you run out of memory rather quickly.

접기

2가지의 아주 쉬운 context와 연관된 메모리누수를 피하는 방법이 있다. 가장 명백한 방법의 하나는 그 Context자신의 영역의 외부로 빠져나가는것을 완전히 피하는것이다. 위 코드는 static을 사용하는 경우를 보여주었다. 하지만 내부 class나 그것의 내부참조를 외부의 클래스에 참조하도록 하는것은 똑같이 아주 위험하다. 
또한가지 방법은 Application context를 사용하는것이다. 이는 액티비티 생명주기와 관계없이 당신의 application이 살아있는동안 항상 유지된다. 만일 아주 오랬동안 유지되어야 하는 객체를 사용해야 한다면 applicaiton객체를 기억하라. Application context는 Context.getApplicationContext()나 Activity.getApplication()을 통해 아주 쉽게 얻을수 있다.

Context연관 메모리누수를 피하는 방법을 정리하겠다. 아래 사항을 기억하라.

  • 오랬동안 유지되어야 하는 레퍼런스는 Activity context에 유지하지 말아라(그것들은 액티비티와 동일한 생명주기를 갖게 된다.) - Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
  • Application context를 사용할것을 시도하라 - Try using the context-application instead of a context-activity
  • 액티비티의 생명주기를 관리하지 않는다면 액티비티내에 non-static inner class를 사용하는것을 피하라. static inner class를 사용해야 하며 액티비티내에서 weak reference를 사용하라.  Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
  • GC는 메모리 누수에 대해 아무런 보장을 하지 않는다. - A garbage collector is not an insurance against memory leaks
Posted by dlucky