안드로이드 버전 3.0 이상에서 UI Thread에서 인터넷 연결시 runtime 에러 안 나게 하는 법

안드로이드 버전 3.0 이상부터는 인터넷 연결은 쓰레드나 핸들러에서 처리하도록 정책이 바뀌었다. 그래서 UI 쓰레스에서 인터넷 연결을 시도하면(HttpURLConnection과 같은 것으로) 실행 타임에서 에러가 발생한다. 


그런데 아래와 같은 코드를 인터넷 연결을 시도하는 코드 앞에

표시해 두면 안드로이드 버전 3.0 이상에서도 정상적으로 잘 실행이 된다. 

onCreate()에 다음과 같이...


    @Override

    public void onCreate(Bundle savedInstanceState) {

     if (Build.VERSION.SDK_INT > 9){

      StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();

      StrictMode.setThreadPolicy(policy);

     }

     

      super.onCreate(savedInstanceState);

      setContentView(R.layout.get_signature_from_toodledo);

        

      txt = (TextView)findViewById(R.id.txt);

    }





HttpURLConnection에 대한 개괄적 개념


HttpURLConnection은 인터넷을 통해 원격의 서버에 연결하는 전문 클래스인데 연결 상태를 확인하게 해 주는 전문 클래스로 정리하면 되겠다. 

이때 연결 상태를 알수 있는 메소드는


getResponseCode()와 getResponseMessage()이다.


HttpURLConnection 객체를 얻는 방법은 URL의 메소드인 openConnection()으로 얻어낼 수 있다.


URL url = new URL("http://www.naver.com");

HttpURLConnection con = null;

con = (HttpURLConnection)url.openConnection();



ping을 통해서 네트워크 연결 상태 체크하기 - 안드로이드 소스 코드에서


Runtime runTime = Runtime.getRuntime();


String host = "192.168.0.13";

String cmd = "ping -c 1 -W 10 "+host; //-c 1은 반복 횟수를 1번만 날린다는 뜻

Process proc = null;


try {

proc = runTime.exec(cmd);

} catch(IOException ie){

Log.d("runtime.exec()", ie.getMessage());

}


try {

proc.waitFor();

} catch(InterruptedException ie){

Log.d("proc.waitFor", ie.getMessage());

}


//여기서 반환되는 ping 테스트의 결과 값은 0, 1, 2 중 하나이다.

// 0 : 성공, 1 : fail, 2 : error이다.

int result = proc.exitValue();


if (result == 0) {

Log.d("ping test 결과", "네트워크 연결 상태 양호");

} else {

Log.d("ping test 결과", "연결되어 있지 않습니다.");

}



※ ping 명령어 옵션들


-4, -6 Force IPv4 or IPv6 hostname resolution

-c CNT Send only CNT pings

-s SIZE Send SIZE data bytes in packets (default=56)

-I iface/IP Use interface or IP address as source

-W timeout Seconds to wait for the first response (default:10)

                (after all -c CNT packets are sent)

-w deadline Seconds until ping exits (default:infinite)

                (can exit earlier with -c CNT)

-q               Quiet, only displays output at start and when finished


안드로이드의 기반이 리눅스이기 때문에 DOS에서의 ping 명령어 옵션과는 약간의 차이가 있다.


-c 옵션은 ping을 날릴 횟수이다.

-W 옵션은 ping을 날린 이후 리턴 결과를 기다릴 timeout 시간 값이다. 연결 상태가 끊겼거나 연결 상태가 좋지 않을때 이 대기 시간 동안 응답이 없으면 연결 실패로 간주한다.


위의 옵션들은 대소문자를 구분한다.

따라서 ping -C xxx.xxx.x.x는 잘못된 사용법이다.







안드로이드 앱에서 구글, 네이버, 다음의 검색 기능을 이용한 웹 검색하는 기능은 Intent를 이용해서 구현할 수 있다.


Intent의 action 가운데 Intent.ACTION_WEB_SEARCH가 이 기능을 안드로이드 시스템에 요청하는 action이다.

이 action을 Intent에 담아서 startActivity로 날리면 안드로이드 시스템은 웹 search를 위한 동작을 수행하게 된다.

기본적으로는 구글 검색을 수행하지만 만일 디바이스(폰)에 네이버 앱이나, 다음 앱이 설치되어 있다면 이들 셋 중에서 선택하는 창이 뜨고 이 창에서 구글, 네이버, 다음의 검색 기능을 이용해서 특정 웹 검색을 수행할 수 있다.




이때 검색할 단어(내용)을 설정하는 것은 Intent의 extra에 담아서 수행하면 된다.

이때 putExtra에 사용할 key를 안드로이드 시스템에서 미리 설정해 두고 있다. 

그것이 SearchManager.QUERY라는 key값이다.

구글(네이버, 다음...)의 웹 검색 창에서 무엇을 검색할지를 안드로이드 시스템에게

알리는데 key 값이다.


Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);

intent.putExtra(SearchManager.QUERY, "검색할 단어");


if (intent.resolveActivity(getPackageManager()) != null) {

     startActivity(intent);

} else {

     String msg = "Sorry, there is no web browser available"; 

     Toast.makeText(this, msg, Toast.LENGTH_LONG).show();

}


Context 클래스의 메소드인 getPackgaeManager() 메소드는 다음과 같다.


public abstract PackageManager getPackageManager () 

⇒ Return PackageManager instance to find global package information.


디바이스에 있는 전 패키지 정보를 획득하기 위한 PackageManger 객체를 반환해 주는 메소드이다.


public ComponentName resolveActivity (PackageManager pm) : Intent 클래스의 메소드

⇒ Return the Activity component that should be used to handle this intent. 


resolveActivity() 메소드는 Intent를 수행할 Activity를 반환한다. 만일 수행할 Activity가 없으면 null을 반환한다.

이럴때 startActivity()를 수행하면 앱이 강제종료를 먹게된다.


그런데 아래 코드의 경우 웹 검색할 앱(Activity)가 구글, 네이버, 다음이 폰에 설치되어 있다면

아래 코드에서는 com.android.internal.app.ResolverActivity가 반환이 된다.

ResolverActivity는 안드로이드 API reference에서 search를 해도 보이지 않는다.

통상적인 클래스들은 API reference에서 search를 하면 자동완성 기능으로 제시가 되는데

ResolverActivity는 표시가 안된다. 

결론적으로 ResolverActivity는 여러개의 Activity가 Intent에 매칭될때 이를 관리하는

클래스이다.




현재 시간을 24시간제로 표시하고자 한다면 android.text.format.DateFormat라는 클래스를 이요하면 된다.

이 클래스가 제공하는 메소드 중에서 다음 메소드가 이 기능을 제공한다.


public static CharSequence format (CharSequence inFormat, long  inTimeInMillis)


첫 번째 매개인자 inFormat에 사용되는 형식은 아래 표의 규칙대로 하면된다

(http://developer.android.com/reference/java/text/SimpleDateFormat.html 참조)


SymbolMeaningKindExample
Dday in year(Number)189
Eday of week(Text)E/EE/EEE:Tue, EEEE:Tuesday, EEEEE:T
Fday of week in month(Number)(2nd Wed in July)
Gera designator(Text)AD
Hhour in day (0-23)(Number)0
Khour in am/pm (0-11)(Number)0
Lstand-alone month(Text)L:1 LL:01 LLL:Jan LLLL:January LLLLL:J
Mmonth in year(Text)M:1 MM:01 MMM:Jan MMMM:January MMMMM:J
Sfractional seconds(Number)978
Wweek in month(Number)2
Ztime zone (RFC 822)(Time Zone)Z/ZZ/ZZZ:-0800 ZZZZ:GMT-08:00 ZZZZZ:-08:00
aam/pm marker(Text)PM
cstand-alone day of week(Text)c/cc/ccc:Tue, cccc:Tuesday, ccccc:T
dday in month(Number)10
hhour in am/pm (1-12)(Number)12
khour in day (1-24)(Number)24
mminute in hour(Number)30
ssecond in minute(Number)55
wweek in year(Number)27
yyear(Number)yy:10 y/yyy/yyyy:2010
ztime zone(Time Zone)z/zz/zzz:PST zzzz:Pacific Standard Time
'escape for text(Delimiter)'Date=':Date=
''single quote(Literal)'o''clock':o'clock

예를들면


MM : 11, 10과 같이 월 표시를 두 자리 숫자로 표현

MMM : Nov, Oct...와 같이 월 표시를 영문 약어로. 

          한글 폰에서는 그냥 '월'이라는 글자로 표시


MMMM : November, October...와 같이 월 표시를 영문 full name으로 표시


yyyy-MM-dd h:mm => 2013-11-25 3:25


yyyy-MM-dd h:mm a => 2013-11-25 3:25 pm


yyyy-MM-dd k:mm => 2013-11-25 15:25 (24시간제로 표시)



예제 소스는...


String crrTime = System.currentTimeMillis().toString();

String now = DateFormat.format("yyyy MMM dd k:mm", crrTime);

        

TextView txt = (TextView)findViewById(R.id.txt);

txt.setText(now);


여기서 DateFormat은 자바의 DateFormat(

java.text.DateFormat)이 아니라 안드로이드의 DateFormat이다. 

클래스 이름이 동일한 DateFormat은 자바에도 있고 안드로이드에도 있다.


android.text.format.DateFormat;





TextView txt = (TextView)findViewById(R.id.txt);

txt.setLinksClickable(true);

txt.setMovementMethod(LinkMovementMethod.getInstance());

String urlNaver = "<a href='http://www.naver.com'>네 이 버</a><br>";

String urlGoogle = "<a href='http://www.google.com'>구 글</a>";


txt.setText(Html.fromHtml(urlNaver));

txt.append(Html.fromHtml(urlGoogle));





안드로이드 SQLite에 대해서 주의해야 할 사항


SQLiteOpenHelp 클래스의 두 abastract 메소드인 onCreate()와 onUpgrade()가 호출되는 시점에 대해서 주의해야 할, 그리고 알아야 할 사항이 있다.

여기서 MyDB는 SQLiteOpenHelper 클래스를 상속받은 클래스라고 할 경우

(class MyDB extends SQLiteOpenHelper)


 MyDB dbHelp = new MyDB(this);

  ⇒ 이 단계에서는 SQLiteOpenHelper를 상속 받은 MyDB의 생성자만 호출이
      되고 MyDB의 onCreate()는 아직 호출이 안된다.


      SQLiteDatabase db = dbHelp.getReadableDatabase() 혹은  
      dbHelp.getWritableDatabase()가 호출 될 때 비로소

      MyDB의 onCreate()가 호출되서 테이블이 생성이 된다. 


      getReadableDatabase()하는 시점에서 테이블이 없으면

      테이블이 새롭게 생성이 되고 기존 존재하면 그것을 Radable이나 Writable 중
      하나로 open한다.


 MyDB의 onUpgrade()가 호출되는 시점

  ⇒ super(context,"Test.db", null, 2); 의 맨 마지막 인자인 DB의
      version 값이 상향 조정될 때 호출된다.





아래와 같은 에러를 만나면 원인은 쓰레드 안에 쓰레드를 사용하였기 때문에 

오류가 발생하였다.


java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()


해결 방법은 쓰레드 안에 Handler를 새로 선언하여 사용하면 된다.


View.OnClickListener clickSomeThing = new View.OnClickListener(){

@Override

public void onClick(View v){

Handler mHandler = new Handler(Looper.getMainLooper());

mHandler.postDelayed(new Runnable(){

@Override

public void run() {

doSomeMethod(); //쓰레드 안에서 실행되는 쓰레드

}, 0);

}

};



안드로이드 WebView의 lodaData() 에서 한글 깨짐 방지


Load할 data가 "UTF-8" 일때,


WebView mWebView = (WebView)findViewById(R.id.webView1);

mWebView.getSettings().setDefaultTextEncodingName("UTF-8");


StringBuilder sb = new StringBuilder();
sb.append("<html><head></head><body><table cellpadding=\"3\"             
                                                                   cellsapcing=\"10\" width=\"750\">");
sb.append("<tr align=\"left\" bgcolor=\"#E5CC7F\">");
sb.append("<th>번 호</th>");
sb.append("<th>이 름</th>");
sb.append("<th>나 이</th>");
sb.append("</tr>");

... 중간 생략 ...

sb.append("</table></body></html>");


// Android 4.0 이하 버전

mWebView.loadData(sb.toString(),  "text/html", "UTF-8");  


// Android 4.1 이상 버전

mWebView.loadData(sb.toString(),  "text/html; charset=UTF-8", null);  


assets 폴더에 있는 HTML 파일을 WebView에 표시하는 방식도 있는데

본 소스는 메모리 상의 HTML을 막바로 WebView에 표시하는 방식이다.




EditText로 입력을 받을 때 숫자만 입력 받고 문자가 포함되었을 경우 다시 
입력받도록 하기위한 간단한 코드이다.


xml layout이 다음과 같을 경우 
inputType="number"로하면 오직 양의 정수만 입력 가능하다. 
키보드에 특수 문자나 +, -, 소수점이 표시되더라도 오직 양의 정수만 입력
가능하다.

그리고 maxLength="4"로하면 4개의 숫자만 입력이 가능하다.
xml layout 파일을 아래와 같이 설정함을 통해서 숫자만 입력되도록 할수 있다.

    <EditText 
        android:id="@+id/editPhoneNum"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/txtPhoneNum"
        android:inputType="number"
        android:maxLength="4"
        android:textSize="40sp"/>

xml layout을 이용하거나 혹은 소스 상에서 체크할려면 다음과 같이 하는 방법도 
가능하다.

EditText editPhone = (EditText)findViewById(R.id.editPhoneNum);
try {
String str = editPhone.getText().toString().trim();

//아래 str에 숫자만 있을 경우는 NumberFormatException이 
                //발생치 않으나 숫자 이외의 어떤 문자가 포함되면 
                //NumberFormatException이 발생한다.
//따라서 이 Exception이 발생하면 숫자 이외의 값이 입력되었다고 
                //판단하고 재 입력 받도록 처리하면된다.

int rt = Integer.parseInt(str);
Toast.makeText(this, "RT : "+rt, 1).show();
} catch(NumberFormatException e){
Toast.makeText(this, "숫자만 입력하세요", 1).show();
}

한 마디로

Integer.parseInt()를 이용하되 NumberFormatException을 이용해서 
처리할 수 있다.

+ Recent posts