반응형

*개발 도구: Eclipse ADT Plugin (Android Developer Tools)

*자바(Java) 버전: 1.8.X


개인적으로 아직까지는 이클립스(Eclipse)에 플러그인 형태로 이미 안드로이드 개발 환경이 덧붙여진 툴을 선호하는 탓에, ADT에서 안드로이드 앱 개발을 하고 있다.

Play Store에 개발자 등록을 하고 나서 그냥 [프로젝트 이름]/bin/ 디렉토리에 있는 APK 파일을 바로 업로드하면, 아래와 같은 메세지를 보이면서 진행이 되지 않는다.


업로드 실패

zip 정렬되지 않은 APK를 업로드했습니다. APK에 zip 정렬 도구를 실행한 다음 다시 업로드해야 합니다.


저런 식으로 표현을 하니까 사실 무슨 의미인지 잘 와닿지 않지만, 대략 전자 서명을 받은 (digitally signed) 앱만 배포 가능하다는 조건인 것 같다. Android Studio에서 Digital Sign 과정을 거치고 zip 정렬이 된 APK 파일을 만드는 방법은 아래 구글 공식 페이지에 적혀 있다.


http://developer.android.com/tools/publishing/app-signing.html#debug-mode


그 외에 zipalign 이라는 콘솔 프로그램을 활용하는 방법도 같이 설명되어 있지만, 아쉽게도 내가 쓰고 있는 ADT 환경에서의 방법은 설명되어 있지 않고 (아마 ADT가 구형이라서 더 지원하지 않는 것일 지도...), zipalign 이라는 프로그램 역시 ADT 내부에 있는 sdk 디렉토리를 찾아봐도 없다.


ADT 환경에서 좀더 쉽게 zip 정렬하는 방법을 찾아본 결과, 아래와 같이 할 수 있다.



(1) 배포하려는 안드로이드 프로젝트 아이콘의 오른쪽 단추를 눌러서 뜨는 메뉴에서 "Export..."를 누르고, Android > Export Android Application 선택.




(2) 배포하고자 하는 안드로이드 프로젝트 이름을 선택하고,




(3) Keystore를 선택해서 암호를 입력한다.

Keystore가 없을 경우, "Create new keystore" 항목을 선택하고 새로운 암호와 함께 만들면 된다.

Location은 안드로이드 앱 배포를 위한 keystore 파일을 보관하기 좋은 위치 아무 곳이나 상관이 없고, (필자는 ADT 플러그인 폴더 내부에 keysotre 라는 위치를 새로 만들었다.) 파일 이름 또한 아무렇게나 해도 상관이 없으며, 확장자를 jks로 한다.




(4) Key alias selection 이라는 항목도 없을 경우 새로 만들어서(create new key) 적당한 암호를 설정해 주면 된다.




(5) Destination APK file 에는 zip 정렬이 되어서 생성될 APK 파일을 저장할 위치를 원하는 아무 곳으로 지정하면 된다.

이 화면에서는 인증서의 유효기간과 MD5, SHA1 같은 값들을 보여준다.

Finish 버튼을 누르면 창이 사라지고, 잠시 후에 지정한 위치에 가보면 zip 정렬된 apk 파일이 생성되어 있다.






*그외 자주 실수하는 부분:

Play Store에 변경된 새로운 apk 파일을 올리려면, 무조건 AndroidManifest.xml 파일에서 Version code 숫자를 바꿔야 한다. 참고로 Version name은 변경하지 않아도 상관 없다.





반응형
블로그 이미지

Bryan_

,
반응형

Java 버전이 올라가면서 향상된 for 루프문(enhanced for loop) 문법이 나왔는데, 이것으로는 배열을 초기화할 수 없다는 사실을 발견했다.


예를 들어, 10개짜리 문자열 배열(String array)을 만들고 향상된 for 루프를 쓰게 되면 아래와 같이 코딩할 수는 있는데,

String[] strArray = new String[10];

for( String str : strArray ){

str = "test_string";

}


for( String str: strArray ){

System.out.println(str); // NullPointerException 발생

}


위와 같이 하면 배열의 각 문자열이 초기화되지 않고 여전히 null 상태로 존재하게 된다.

번거롭더라도 배열 초기화는 index number를 사용해서 아래와 같이 명시적으로 해 주는 것이 좋다.


for(int i=0; i<strArray.length; i++){

strArray[i] = "test_string";

}


이 문제는 String 뿐만 아니라 객체 배열(Object array), 기본 타입 배열(primitive type array; 예를 들어, int[], double[])에서도 마찬가지로 발생한다.

다만 기본 타입(primitive type) 배열의 경우에는 향상된 for 루프를 쓰더라도 NullPointerException은 발생하지 않지만, 값들이 0인 상태로 바뀌지 않은 채 존재할 것이다.



반응형
블로그 이미지

Bryan_

,
반응형

*테스트한 Eclipse version: JUNO


이클립스(Eclipse IDE)를 쓰다가 느 날, 왼쪽의 Package Explorer 창에서 각 프로젝트 옆에 표시되는 SVN 관련 정보 (revision 번호, SVN repository 주소 등)가 사라져 있는 것을 발견했다.

설정을 바꾸지 않는데도 이렇게 갑자기 보이지 않게 되는 것이 이상하지만, 어쨌든 이클립스를 새로 설치할 필요 없이 설정을 변경해서 다시 복구할 수 있다.



(1) 메뉴에서 Window > Preferences 선택


(2) General > Appearance > Label Decorations 선택

오른편에서 "SVN" 항목이 선택되어 있지 않다면, 체크해서 선택한다.





추가로, SVN 관련 표시에 대한 설정(글자색, 아이콘 등)을 변경하고자 한다면, 

Team > SVN > Label Decorations 에 가면 된다.




반응형
블로그 이미지

Bryan_

,
반응형

Development environment: Oracle Java 1.7


This post shows a case when the "createTextNode" method with the null parameter causes "javax.xml.transform.TransformerException: java.lang.NullPointerException" without detailed information (difficult to find the exact line of code causing NullPointerException).


Using DocumentBuilderFactory and TransformerFactory, users can create an XML document with user-defined tags. Usually, I convert a user-defined object into the XMlL representation in String using DocumentBuilderFactory and TransformerFactory. Below is a simple example which causes a NullPointerException:


/* Person.java */

public class Person {

String name;

String phone;


public Person(){ }


public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getPhone() {

return phone;

}

public void setPhone(String phone) {

this.phone = phone;

}

}


/* TestMain.java */

public class TestMain {

public static void main(String[] args){

Person person = new Person();

// person.setName("Smith"); // commented for making NullPointerException

person.setPhone("01011112222");


try {

DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();

DocumentBuilder docBuilder = dbfac.newDocumentBuilder();

Document doc = docBuilder.newDocument();

// root (person) tag

Element ePerson = doc.createElement("person");

doc.appendChild(ePerson);

// name tag

Element eName = doc.createElement("name");

ePerson.appendChild(eName);

Text nameText = doc.createTextNode(person.getName());

eName.appendChild(nameText);

// phone tag

Element ePhone = doc.createElement("phone");

ePerson.appendChild(ePhone);

Text phoneText = doc.createTextNode(person.getPhone());

ePhone.appendChild(phoneText);

//set up a transformer

TransformerFactory transfac = TransformerFactory.newInstance();

Transformer trans = transfac.newTransformer();

trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

trans.setOutputProperty(OutputKeys.INDENT, "yes");

//create string from xml tree

StringWriter sw = new StringWriter();

StreamResult result = new StreamResult(sw);

DOMSource source = new DOMSource(doc);

trans.transform(source, result); // the line where the Exception indicates

// print the XML output

System.out.println(sw.toString());

} catch (Exception e) {

e.printStackTrace();

}

}

}


As the TestMain.java shows, if the member variable "name" in the Person class is not set, it generates an exception like below:

ERROR:  ''

javax.xml.transform.TransformerException: java.lang.NullPointerException

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)

at org.usera.xml.TestMain.main(TestMain.java:39)

Caused by: java.lang.NullPointerException

at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.characters(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(Unknown Source)

... 3 more

---------

java.lang.NullPointerException

at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.characters(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)

at org.usera.xml.TestMain.main(TestMain.java:39)


The exception guides me to "trans.transform(source, result)", but it doesn't seem to contain any null parameter. This means the other parts in the XML document object contains null values.

In this example, we can quickly find that the member variable "name" should be initialized with a valid String because it is a very simplified code. However, if I handle complex  objects including tens of member variables, it is not easy to find which variables are set to null.


Thus, I tried to make a wrapping method which make the "createTextNode" always get a "not null" parameter. The simple solution might be like below:

public static Text createTextNodeWithoutNull(Document doc, String str)

{

Text textNode;

if(str != null) textNode = doc.createTextNode(str);

else textNode = doc.createTextNode("null");

return textNode;

}


Then the modified code (a part of TestMain) is:

// name tag

Element eName = doc.createElement("name");

ePerson.appendChild(eName);

Text nameText = createTextNodeWithoutNull (doc, person.getName());

eName.appendChild(nameText);

// phone tag

Element ePhone = doc.createElement("phone");

ePerson.appendChild(ePhone);

Text phoneText = createTextNodeWithoutNull (doc, person.getPhone());

ePhone.appendChild(phoneText);


Likewise, the XML Document object won't contain any null values inside.



반응형
블로그 이미지

Bryan_

,
반응형

테스트 기기: 엔스퍼트 아이덴티티탭 (E201V)

안드로이드 버전: 이클레어(Eclair, 2.1), 프로요(Froyo, 2.2)



연구실에서 개발한 테스트용 앱을 아이덴티티탭(안드로이드 2.1)에 설치하려고 했는데 "응용프로그램이 설치되지 않았습니다."라는 메세지와 함께 아래와 같은 설치 실패 화면만 보게 되었다.


(그림 1) 아이덴티티탭에 설치되지 않는 프로그램



예전 개발환경에서는 (이클립스 + 안드로이드 sdk 수동설치) 문제없이 설치가 되었는데, 최근에 ADT (Android Developer Tools)에서 같은 코드로 프로젝트를 만들고 설치를 시도하니 안된다. Apk 파일만 SD카드에 따로 복사해서 "알 수 없는 소스" 옵션을 체크하고 설치를 시도해도 되지 않았다.

혹시 안드로이드 버전이 너무 낮아서 그런가 해서 아이덴티티탭을 프로요(2.2)로 펌웨어 업데이트를 했는데도 문제는 해결되지 않았다.



*해결의 실마리 (?)

예전 개발환경에 있던 프로젝트와 ADT에 있는 프로젝트의 차이점이 하나 있었는데, 참조하는 라이브러리 jar 파일의 위치였다.

예전 개발환경 (이클립스 + 안드로이드 sdk 수동설치)에서는 프로젝트 밑에 아무 폴더를 만들고(예: lib), 그 안에 jar 파일을 집어넣었다. 그리고 이클립스의 Java Build Path에서 해당 jar 파일을 참조하도록 선택했다.


반면에 ADT를 비롯해서 비교적 최신 버전의 안드로이드 개발환경(이클립스 플러그인)에서는 프로젝트 밑에 "libs" 폴더를 만들고, 그 안에 jar 파일을 복사해 넣으면 자동으로 참조가 된다.


(그림 2) ADT에서 안드로이드 프로젝트의 libs 폴더에 jar 파일을 추가하는 경우


그런데 문제는, 안드로이드 2.1 프로젝트에 대해서 이렇게 libs 폴더를 통해서 jar 파일을 추가해도 프로젝트 설정(Project Properties)에서는 보이지 않는 것이다.


(그림 3) 이상하게 libs에서 추가한 jar 파일 2개가 표시되지 않는다.



그래서 ADT에서도 다시 예전 방식대로 참조해 보았다. libs 대신 그냥 일반적인 폴더(이 예제에서는 lib)를 만들고, 그 안에 jar 파일을 추가하고,


(그림 4) libs 폴더 대신 lib 폴더 사용. 자동으로 jar 파일을 참조하지 않는다.


프로젝트 설정에서 수동으로 jar 파일을 아래 그림 5~그림 6과 같이 추가했다.


(그림 5) "Add JARs..." 버튼을 눌러서 수동으로 추가


(그림 6) 해당 프로젝트의 lib 폴더 밑에 있는 jar 파일을 복수 선택



그리고 Java Build Path 창에서 "Order and Export" 탭으로 가서, 체크되어 있지 않은 2개의 참조된 라이브러리를 체크해 주었다.


(그림 7) Order and Export 탭에서 참조 라이브러리를 체크한다.


위와 같이 설정한 뒤에 프로젝트를 새로 빌드하고 나서 설치를 시도해 보니, 아이덴티티탭에 문제없이 설치가 되었다.



(주의)

이 방법은 오히려 최신 버전의 안드로이드 버전에서는(경험상으로 4.0 이상) 오히려 앱 빌드가 안되는 문제를 초래할 수도 있다. 최신 버전의 안드로이드 플랫폼에서 개발할 때는 libs 폴더에 jar 파일을 추가해서 자동으로 참조되도록 하는 것이 좋다. 또한 jar 파일이 아니고 Projects 탭에 바로 프로젝트를 참조하는 것도 문제를 일으킬 소지가 크다.




반응형
블로그 이미지

Bryan_

,