최근 페이스북 측에서 업데이트하라고 리포트가 와서 로그인 모듈을 업데이트 하는 데 빌드 에러가 떴습니다.


Exception while processing task java.io.ioexception: can't write ~ ~ Duplicate zip entry com.google.zxing~ 



정확히는 기억안나고 대충 이런 내용이었는 데, Facebook 라이브러리가 업데이트되면서 따로 사용 중인 Zxing라이브러리와 중복되는 부분이 있는 것 같습니다. 


implementation ('com.facebook.android:facebook-android-sdk:4.38.1') { exclude group: 'com.google.zxing' } 


이렇게 exclude 부분을 추가해주면 정상적으로 빌드되는 것을 확인하실 수 있습니다. 


추가로, 현재 4.39.0까지 나왔는 데 iOS쪽에서 로그인 버튼이 안눌러지는 버그가 발견되었다고 합니다. 

4.39.0으로 업데이트 하시는 분들은 꼭 확인해보시고 적용하시길 바랍니다.

   Databinding을 활용하여 작업하는 중 다른 프로젝트를 동시에 켜서 실행하다가 다시 빌드했더니 생긴 문제입니다.

처음엔 generated 폴더에서 에러가 나서 Invalid Caches / Restart를 실행하였으나 실패했고, 재부팅을 하여도 같은 결과가 나왔습니다.


 LoggedErrorException found data binding errors


   위와 같은 문제가 생길 시 프로젝트 하위에 있는 .idea와 .gradle을 삭제 후 Clean-build를 하시면 다시 정상적으로 동작하는 것을 보실 수 있습니다.




출처 : https://stackoverflow.com/questions/44594475/listener-binding-cannot-find-the-setter

  오늘 인수인계 받은 코드를 안드로이드 스튜디오 내에서 버전업하다가 2시간을 버리게 만든 에러가 있었습니다. 



unable to resolve dependency for could not resolve multidex 1.0.2


  메소드가 64k이상인 경우 빌드가 가능하도록 도와주는 multidex의 에러인 데.. 아무리 찾아보고 해결 방법을 적용해봐도 수정이 안돼서 고생하다가 겨우 찾았습니다. 스택오버플로우나 기타 개발 블로그들에서 제시한 것들 중 다 안되는 분은 적용해보시면 좋을 것 같습니다.


  생각보다 간단합니다. 그냥 build.gradle에



dependencies {
   compile 'com.android.support:multidex:1.+'
}


넣어주면 끝입니다.

비슷하게 제시한 해결법이 multidex:1.+ 대신 multidex:1.0.2를 넣는 것인데.. 

저의 경우는 실패했고 예전에 비슷한 케이스가 생각나서 1.+로 바꿔주니 됐습니다.

하는 김에 이번엔 aar 파일을 만들어보겠습니다.


aar파일을 만들기 원하는 프로젝트의 build.gradle에 들어가셔서, 아래 소스를 써주시면 됩니다.

buildTypes {

      release {

            libraryVariants.all { variant ->

                  variant.outputs.each { output ->

                        def outputFile = output.outputFile

                        if (outputFile != null && outputFile.name.endsWith('.aar')) {

                              def fileName = "CreabySampleLibrary.aar"

                              output.outputFile = new File(outputFile.parent, fileName)

                        }

                  }

            }

       }


혹시 오타 났을까봐;; 이미지로도 올리겠습니다.



이렇게 작성 후 빌드하시면 build -> output 폴더가 생성되면서 aar파일이 있을겁니다.

그걸 그대로 사용하시면 됩니다.

이번엔 라이브러리 프로젝트를 만들어보겠습니다.

기능에 따라 프로젝트의 분리가 필요하거나 라이브러리를 만들 때 사용하는데요.

매우 쉽게 만드실 수 있을겁니다.


구현 환경은 Android Studio 2.3.3 Mac버전 입니다.



기본적 바탕은 그냥 일반적인 프로젝트를 만들어주시면 됩니다.

Empty Activity를 선택하시고요.

.

.

.

만드셨으면 이제 라이브러리 프로젝트로 변경해보겠습니다.


1. 메니페스트에 들어가서 아래와 같이 package명만 놔두고 전부 지워줍니다.




2. build.gradle에 들어가서 apply:plugin의 com.android.application을 com.android.library로 바꿔주시고, applicationId를 지워줍니다.

끝났습니다.

참 쉽죠?


적용할 땐 적용하고 싶은 어플리케이션 프로젝트에서 File -> New -> Import Module에 들어가셔서 추가해주시고, File -> Project Structure 들어가셔서 좌측 탭에 있는 Modules에서 어플리케이션 프로젝트 -> Dependencies -> + 누르셔서 해당 프로젝트를 추가해주시면 됩니다.




자꾸 까먹는 데 구글링하면 방식이 여러가지인 것 같아서 그냥 기록 겸 올립니다.

이런 기본적인 것도 자주 까먹는 걸 보면 아직 전 멀은 것 같네요 ㅠㅠ


적용하려는 프로젝트의 build.gradle 에서


repositories {

      flatDir {

         dirs 'libs'

      }

}


이렇게 라이브러리 경로를 설정해주신 후, (폴더가 없으시면 없으시면 src와 build폴더가 있는 경로에 직접 폴더를 만드시면 됩니다.)



dependencies {

      compile(name: 'HelloWrold', ext: 'aar') 

}


이렇게 해주면 끝납니다.


추가 (2017. 11. 02) : 라이브러리를 추가하면 꼭 Clean Project와 Build를 해주셔야합니다. 안하면 적용이 안되는 문제가 생길 수 있습니다.


참 쉽죠?

근데 전 자꾸 까먹네요..

안드로이드 개발하면서 다들 한 번쯤 봤을 에러일겁니다.


java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader 



so파일을 사용할 때 연동이 안된다고 뜨는 에러인데요.

jniLibs에 종류별로 모두 들어가있어야할 so파일이 어딘가 비어있는 데 모바일 디바이스 기종 상 so파일이 없는 곳을 참조하려고 해서 생기는 문제입니다. 이럴 때는 딱 두 부분만 추가해주면 됩니다.


gradle.properties에 가셔서 android.useDeprecatedNdk=true를 써주시고

so파일을 사용하는 프로젝트의 gradle에 가셔서 defaultConfig 안에


defaultConfig {
   ndk {
      abiFilters 'arm64-v8a', 'armeabi'
   }
}


이런 식으로 해당 so파일이 있는 폴더만 적어주시면 됩니다.


ABI 문제로 인해 디바이스 충돌 사례가 자주 발생하니 앱 개발 시 다양한 NDK를 사용한다면 지원하는 ABI를 미리 정하고 모두 통일시켜서 같은 ABI만 사용하셔야 위와 같은 안봐도 될 불필요한 에러를 발생시키지 않으니 꼭 잘 확인하고 개발하시는 것이 좋습니다.



추가 (2017.11.01) : so파일이 두 종류가 있는 데 하나는 armeabi, armeabi-v7a가 있고 하나는 armeabi, arm64-v8a가 있는 상황인 경우, 둘 다 사용하기 위해 armeabi, armeabi-v7a, arm64-v8a를 적으면 Crash가 납니다. 모두 있는 경우만 사용 가능하니 유의해주세요.


참고

해결법 https://stackoverflow.com/questions/37884769/unsatisfiedlinkerror-dalvik-system-pathclassloader

설명 https://developer.android.com/studio/projects/add-native-code.html?hl=ko

SQLite를 사용하기 때문에 별 생각없이 CursorAdapter로 Listview를 구성하면 DB를 수정, 삭제하거나 Listview의 정렬방식 변경, 검색등과 같이 Listview상의 순서를 기존과 다르게 변경하는 경우 Item position 값이 꼬이는 문제가 생깁니다. 그래서 일반적으로 ArrayAdapter를 사용하는 경우처럼 Listview상의 Item을 터치하여 해당 Item값을 불러오는 Activity를 띄우려고 할 때 setOnItemClickListener에서 제공하는 position값을 그대로 사용하면 엉뚱한 결과값이 출력되거나 아에 죽어버리는 상황을 맞이하게 됩니다.




간단히 DB 삭제의 경우를 예로 들어보겠습니다.

위와 같이, setOnItemClickListener에서 제공하는 position값으로 DB를 삭제해버리는 경우, 겉으로 보기에는 제대로 지워진 것처럼 보이지만 실제로는 ListView의 position값과 DB값은 꼬여버립니다. 만약 position=1에 해당하는 item을 삭제한 후에 position=2에 해당하는 아이템을 터치하면 D에 해당하는 DB값이 출력되는 게 아니라 C에 해당하는 DB값이 출력될겁니다. 물론 position=1에 해당하는 아이템을 다시 터치하게 된다면 index=1이었던 DB값은 삭제된 후이기 때문에 예외처리를 안했다면 죽는 문제가 생기겠죠.


따라서, Listview의 position값을 그대로 사용하는 게 아니라 position에 해당하는 DB의 _id값을 불러와서 제어하는 것이 좋습니다.


listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
   @Override
   public void onItemClick(AdapterView adapterView, View view, int position, long l) {
      Intent intent = new Intent(MainActivity.this, InfoActivity.class);

      Cursor cursor = (Cursor) cursorAdapter.getItem(position);
      String index = cursor.getString(cursor.getColumnIndex("_id));
      int id = Integer.parseInt(index);

      intent.putExtra("id", id);
      startActivityForResult(intent, 0);
   }
});


먼저 getItem으로 현재 position에 있는 item값을 불러오고, 그 중 getColumnIndex로 _id값을 불러와서 그 id값을 기준으로 불러오시면 정상적으로 원하시는 DB값이 호출되는 것을 보실 수 있습니다.




참고


http://arabiannight.tistory.com/entry/368

안드로이드 개발하면서 절대 빠질 수 없는 것 중 하나가 AsyncTask일겁니다.

그만큼 AsyncTask의 개념이나 사용법에 대한 설명이 매우 많은 데, 중요하지만 자주 언급되지 않는 기능 하나를 소개하겠습니다.




AsyncTask를 사용하게 되면 일반적으로 현재 이 프로그램이 실행되는 지 아닌 지 확인하기 위해 Dialog를 띄웁니다. 문제는 작업량이 많아서 로딩이 길어지는 경우인 데, 로딩 중에 사용자가 무심코 주위를 터치하게 되면 Dialog창이 꺼져버려서 현재 이게 먹통이 된건지, 어느 정도 처리가 된 상태인 지 알 방도가 없습니다. 버튼 이벤트에 대한 처리를 딱히 안했다면 그 상태에서 중복터치를 해서 프로그램이 완전히 꼬여버리는 문제까지 생기기도 합니다. 이를 방지하기 위해 있는 기능이 setCanceledOnTouchOutSide(boolean)입니다.


@Override
protected void onProgressUpdate(Void... values) {
   dialog.setCanceledOnTouchOutside(false);
}

@Override
protected void onPostExecute() {
   dialog.setCanceledOnTouchOutside(true);
   dialog.dismiss();
}


어렵지 않죠? 하지만 로딩 시간이 1초만 돼도 꼭 필요한 기능입니다.

물론 그 이하도 웬만하면 넣어주시는 게 안정성을 향상시키는 데 있어서 좋다고 생각합니다.




출처 : https://developer.android.com/reference/android/app/Dialog.html#setCanceledOnTouchOutside(boolean)

이번엔 메모리 관리 시 주의해야할 부분에 대해 알아보겠습니다.


안드로이드에서 메모리는 VM Heap과 Native Heap을 사용하는 데, OutofMemoryError의 해결책을 찾기 위해 구글링을 열심히 하다보면 자주 볼 수 있는 내용 중 하나가 "Honeycomb 버전부터는 안드로이드에서 관리되는 비트맵은 모두 VM Heap에서 관리한다." 입니다. (제가 제대로 이해한건지는 모르겠지만;)


실제로 이전 글에 썼던 것과 같이 Recycle을 적절히 활용해주면 중간중간 메모리를 찍어봤을 때 VM Heap에 메모리가 쌓이고 줄어드는 것을 알 수 있습니다. 굳이 찍기 귀찮다 싶으면 안드로이드 스튜디오 상에서 Android Monitor -> Monitors 탭으로 들어가서 정확하진 않아도 가시적으로나마 확인할 수도 있죠.


그런데 로그상에서 메모리 관리가 제대로 되는 걸 확인한 상태임에도 불구하고 수십번을 반복하다보면 어느 순간 에러도 없이 죽어버리는 경우가 있습니다. 저같이 로그와 디버깅만 믿고 살아가는 초보 개발자에겐 정말 멘탈 터지는 상황이 오는거죠. 다른 케이스가 또 있을지는 모르겠으나, 제가 발견한 원인은 Native heap의 메모리 축적이었습니다. 이전 글에서 밝혔다시피, 메모리 문제가 심하게 터졌던 이 프로젝트에는 영상처리가 들어가있었기 때문에 NDK가 적용되어 있는 상태였는 데, Release 함수를 적용한 상태라 신경을 안쓰고 있었는 데, 영상처리 파트에서 메모리 관리를 잘못해서 Native heap이 쌓이고 있었던 겁니다. 그러니 평소에도 내 영역이 아니더라도 내게 영향을 미칠 수 있다면 확인을 하는 습관을 들이는 게 좋을 것 같습니다.



소스도 없고, 그림도 없고, 주저리 주저리 쓸데없는 일기들만 가득찬 글만 있어서 읽기 싫은 분들을 위해 요약해드리자면,


1. 비트맵을 NDK로 넘길 때는 VM Heap이 아니라 Native Heap에서 관리한다. <- 전 이거 몰랐습니다ㅠㅠ

2. 그러므로 NDK를 사용하고 있는 상태라면, Release 함수를 적용하더라도 중간 중간에 Native Heap Size의 로그를 찍어본다.

3. 만약 문제가 있다싶으면 담당자 찾아가서 담당자의 직책에 따라 태세변환하며 메모리 관리 부분을 확인해줄 것을 요청한다.



- 참조 사이트 http://d2.naver.com/helloworld/539525 

                  -> 메모리 관리에 대해 하나부터 열까지 정리되어 있습니다. 정독하면 메모리 관리에 도움이 되실 겁니다.

+ Recent posts