기본 기능을 구현한 뒤 app market에 올렸다가 debugging phone에서는 막힘없이 되던 부분에서 app이 멎어버리는 bug를 접했다. logcat을 보니 library에서 class를 읽어들이는데 문제가 있는 모양인데 분명히 gradle에도 해당 .jar 파일들을 추가했고 이미 정상적으로 동작하는 폰도 있는데 왜 bug가 잡힌 것일까? bug가 잡힌 phone은 API 19고 되던 폰은 API 21이긴 한데 말이다.

E/dalvikvm: Could not find class 'org.apache.poi.ss.util.CellRangeAddress', referenced from method org.apache.poi.hssf.usermodel.HSSFWorkbook.setRepeatingRowsAndColumns
W/dalvikvm: VFY: unable to resolve new-instance 7124 (Lorg/apache/poi/ss/util/CellRangeAddress;) in Lorg/apache/poi/hssf/usermodel/HSSFWorkbook;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0009
W/dalvikvm: Unable to resolve superclass of Lorg/apache/poi/ss/util/CellRangeAddress; (7125)
W/dalvikvm: Link of class 'Lorg/apache/poi/ss/util/CellRangeAddress;' failed
E/dalvikvm: Could not find class 'org.apache.poi.ss.util.CellRangeAddress', referenced from method org.apache.poi.hssf.usermodel.HSSFWorkbook.setRepeatingRowsAndColumns
W/dalvikvm: VFY: unable to resolve new-instance 7124 (Lorg/apache/poi/ss/util/CellRangeAddress;) in Lorg/apache/poi/hssf/usermodel/HSSFWorkbook;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0010

 

  나와 같은 error를 먼저 만나본 사람이 어디없나 검색해 보니 다행히 있긴 있었다. MultiDex 기능이 Lollipop 이전에서는 제대로 동작하지 않을 수도 있기 때문에 아래 code를 class 내에 선언하여야 해당 library의 method들을 활용할 수 있다는 것이다.

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}
[출처] StackOverflow - Android Studio E/dalvikvm﹕ Could not find class '.DatabaseHelper', referenced from method .DatabaseManager
                                  (http://stackoverflow.com/questions/32697460/android-studio-e-dalvikvm-could-not-find-class-databasehelper-referenced-fr

  결과는 성공이었다. 문제를 해결하게 되서 기쁘긴한데 이 해결책을 알아낸 사람들은 도대체 어떻게 찾은건지 ㅎㅎ. 역시 무작정 code만 짜기 보다는 전체적인 동작 원리와 구조를 파악할 줄 아는 것이 진정한 힘인 것 같다.

1. 목적 : PopupWindow의 사용법을 익히고 App.에 적당한 위치에 적용해 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - PopupWindow Reference (http://developer.android.com/reference/android/widget/PopupWindow.html)
 2) App. 제작소 - [Android] PopupWindow 사용 예제 (http://makingappfor.blogspot.kr/2013/05/android-popupwindow.html)
 3) Android-er Blog - Display Spinner inside PopupWindow (http://android-er.blogspot.kr/2013/08/display-spinner-inside-popupwindow.html)

4. 과정
  PopupWindow example을 요약하자면 Popup window가 될 .xml layout을 구성하여 LayoutInflator로 불러 들여 현재 PopupWindow 변수를 통해 View 위에 그려주는 순서로 진행된다.

 1) activity_main.xml(main의 layout)에 클릭시 Popup Window를 불러들일 ImageButton을 추가한다.


<?
xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <ImageButton
        android:id="@+id/exampleimg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:padding="20dp"
        android:src="@mipmap/ic_launcher"/>
</RelativeLayout>

 

 2) PopupWindow의 layout이 될 .xml file을 생성한다. 나 같은 경우에는 App의 종료 여부를 물을 창을 만들 것이므로 TextView 1개와 Button 2개를 다음과 같이 구성하였다. PopupWindow의 역할에 따라 위 참고자료와 같이 spinner를 선언하기도 하고 ImageView를 보여주게 할 수도 있다.


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="50dp">

    <TextView
        android:id="@+id/popupquestion"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:padding="5dp"
        android:layout_margin="10dp"
        android:text="App.을 종료하겠습니까?"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="right"
        android:layout_below="@id/popupquestion">
    <Button
        android:id="@+id/yesbtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:padding="10dp"
        android:text="예"
        android:backgroundTint="@android:color/transparent" />

    <Button
        android:id="@+id/nobtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:layout_toRightOf="@id/yesbtn"
        android:text="아니오"
        android:backgroundTint="@android:color/transparent"/>
    </LinearLayout>
</RelativeLayout>

 

 3) MainActivity.java에 ImageButton Click시 PopupWindow 생성되도록 다음과 같이 설계한다. 보다보면 앞에서 ListView, Gallery 의 Adapter에서 getView() 할 때와 비슷하게 main이 되는 layout에 inflator로 추가 layout을 받아와서 동작하게 하는 방식임을 알 수 있다.

public class MainActivity extends AppCompatActivity {

    public ImageButton exampleImg;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // ImageButton 생성 및 연결. OnClickListener()로 Click시 PopupWindow를 생성하도록 함.
        exampleImg = (ImageButton)findViewById(R.id.exampleimg);
        exampleImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                final PopupWindow popupWindow = new PopupWindow(v); // onClick(View v)로 받아온 View 위에 PopupWindow 생성
                // PopupWindow에 반영할 layout을 다음과 같이 생성 및 연결
                LayoutInflater layoutInflater = (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
                View popup = layoutInflater.inflate(R.layout.layout_popup, null);
                popupWindow.setContentView(popup);
                popupWindow.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                popupWindow.setTouchable(true); // PopupWindow 위에서 Button의 Click이 가능하도록 setTouchable(true);
                Button yesBtn = (Button) popup.findViewById(R.id.yesbtn); // PopupWindow 상의 View의 Button 연결
                Button noBtn = (Button) popup.findViewById(R.id.nobtn);
                yesBtn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        finish(); // App.의 종료
                    }
                });
                noBtn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        popupWindow.dismiss(); // PopupWindow의 해제
                    }
                });
                popupWindow.showAsDropDown(v); // PopupWindow를 View 위에 뿌려 줌. 선언하지 않을 경우, PopupWindow가 보이지 않음
            }
        });
    }
}

(위 Code의 실행 결과)

    

 

[아니오] Button을 누를 경우

[예] Button을 누를 경우 

 

  앞에서 스마트폰에 내장되어 있던 기본 camera 기능을 intent로 불러와 사진을 촬영하는 기능을 살펴보았다. 동작이 제대로 되는 것을 확인했으니 촬영한 사진 경로를 가져다가 ImageView를 받아오는 것은 간단하리라 생각했었다. 그러나 생각은 그냥 생각일 뿐, 역시나 쉽게 되지 않았다. 단순히 Imageview 변수를 선언한 뒤 imageview01.setImageBitmap("Bitmap file"); 혹은 imageview01.setImageURI("URI값");와 같은 method를 사용하면 되리라 생각하고 해당 code를 추가하고 실행해 봤더니 그런 file은 찾을 수 없다는 Log가 나를 반기며, imageview는 텅 빈 채로 출력되는 것이 아닌가?

  아무래도 App에서 직접 camera hardware에 접근하여 사진을 촬영해 저장할 때처럼 FileOutputStream으로 data를 write하고 난 후 바로 해당 파일을 Bitmap 변수로 읽어 ImageView에 반영해야 겠다는 생각이 들었다(이런 방식으로 만들었을 때는 ImageView에 사진이 반영이 안되는 문제는 없었기 때문이다). 그러기 위해 기본 camera intent가 종료된 후 동작할 onActivityResult();를 @Override 하여 다음과 같이 작성하였다.

1. 목적 : Camera App을 직접 만들어 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - API Guide(http://developer.android.com/intl/ko/guide/topics/media/camera.html)
 2) smile8916님 블로그(http://smile8916.tistory.com/92)

4. 과정
 (시작하기에 앞서, 이전 게시글에 따른 Camera intent 관련 설정을 해주어야 한다.)
 1) activity_main.xml(layout을 설정)에 촬영 결과를 보여줄 ImageView를 다음과 같이 선언해 놓는다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:id="@+id/imgpath"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

    <ImageView
        android:id="@+id/resultview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal"
        android:layout_below="@id/imgpath" />
</LinearLayout

 

 2) MainActivity.java(앞서 camera intent를 불러들였던 Activity에 해당)에 intent가 정상적으로 동작한 후에 해당 경로로 FileOutputStream을 이용하여 data를 write 할 onActivityResult()를 다음과 같이 Override 한다.


    @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) // intent 결과 정상적으로 마쳐질 경우
        {
            /* 앞서 수행한 intent request 값이 image capture와 관련된 것일 경우 다음을 수행한다. */
            if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE
            {
                ImageView resultView = (ImageView)findViewById(R.id.resultview); // .xml 에서 선언한 ImageView 변수를 Activity에도 선언한 후
                File tempImg = new File(fileUri.getPath()); // 앞서 지정한 경로에 파일의 경로에
                if(!tempImg.exists()){ // file이 존재하지 않으면
                    Log.d(TAG, "The image doesn't exist : " + tempImg.toString());
                    try {
                        FileOutputStream fos = new FileOutputStream(new File(fileUri.getPath())); // FileOutputStream 변수를 선언한 후
                        try {
                            fos.write(data.getExtras().getByte("data")); // Intent의 data 값을 write 하여 저장
                            fos.close(); // 그러고 나서 FileOutputStream을 종료한다.
                        } catch(IOException e){
                            Log.d(TAG, "IOException occur : " + e.getMessage());
                        }
                    } catch (FileNotFoundException e){
                        Log.d(TAG , "The image doesn't exist : "+ tempImg.toString());
                    }
                }
                Bitmap bm = BitmapFactory.decodeFile(fileUri.getPath()); // 해당 file을 Bitmap으로 decode 하여 저장한 후
                resultView.setImageBitmap(bm); // setImageBitmap() method로 앞서 선언한 ImageView에 촬영한 사진을 입력
                resultView.setRotation(imageViewRotation); // ImageView의 회전
            }
        }
    }

 

(위 code를 실행한 결과) 

 

 

 

 

 

 

 

 

 

 [Camera Intent 촬영 장면]

 [Camera Intent 종료 후 App 화면]

 [촬영된 사진 경로 확인]

 

 

 

 

 

 

 

+ Recent posts