오늘 포스팅할 내용은,


안드로이드 디바이스가 현재


인터넷을 사용할 수 있는 상태인지


또한 사용할 수 있는 상태라면 


어떤 인터넷 환경인지 (Wifi or 3g ,4g ) 알아보는 방법에 대해 기술한다.



인터넷을 사용할 수 있는 상태인지를 체크하기 앞서,


인터넷이 연결되 있는 경우


어떤 인터넷 환경인지 가져오는 것부터 할 것이다.



먼저 AndoridManifest.xml 에 


해당 퍼미션을 등록해준다.


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

인터넷 환경을 탐색하기 위한 멤버변수를 선언 해준다.


public static final String WIFI_STATE = "WIFE";
public static final String MOBILE_STATE = "MOBILE";
public static final String NONE_STATE = "NONE";

그 후 아래의 메서드를 작성한다.


public static String getWhatKindOfNetwork(Context context){
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork != null) {
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
return WIFI_STATE;
} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
return MOBILE_STATE;
}
}
return NONE_STATE;
}


핵심코드는 getActiveNetworkInfo( ) 메서드이다.


해당 메서드는 인터넷이 연결되있는 경우 인터넷 환경에 대한 여러가지 정보를 담은 객체를 리턴한다.


만약 인터넷 환경이 연결되있는 상태가 아니라면 null을 리턴한다.


위의 메서드를 사용할 시, 인터넷 환경이 wifi 상태인지 3g,4g 상태인지 알 수 있다.



이 메서드를 통해서 인터넷이 연결됬는지 확인 할 수도 있지만,


이 메서드만 사용한다면 캐치 못하는 경우의 수가 있다. 



Wifi 연결은 되었지만 로그인, 인증서 등 권한을 획득하지 못하여 인터넷을 쓰지 못하는 경우.



위의 경우의 수에서는 사용자가 인터넷을 사용하진 못하지만 디바이스에서는 wifi가 연결된 걸로 인지한다.


이러한 경우의 수까지 캐치 하고 싶다면 아래의 코딩을 추가 해야한다.



1. Ping 보내기.


 ping이란, 원하는 네트워크 주소에 신호를 보내, 응답이 도착하는가 확인하는 것이다.

 

 만약 인터넷을 사용하지 못한다면, ping 신호는 도달 하지 못할 것이다.


 안드로이드도 리눅스 기반 이므로, 리눅스 명령어를 통해 ping 을 보낼 수 있는데,


 java 에서는 제공해주는 리눅스 api가 없기 때문에,


 밑단까지 내려가 명령을 줘야한다.  아래의 코드를 보자.



 private static class PingTask extends Thread {

private boolean success;
private String host;

public PingTask(String host) {
this.host = host;
}

@Override
public void run() {
super.run();
try {
Runtime runtime = Runtime.getRuntime();
Process proc = runtime.exec("ping -c 1 -W 100" + host);
proc.waitFor();
int exitCode = proc.exitValue();
if (exitCode == 0) {
success = true;
} else {
success = false;
}
} catch (IOException e) {
e.printStackTrace();
success = false;
} catch (InterruptedException e) {
e.printStackTrace();
success = false;
}
}

public boolean isSuccess() {
return success;
}
}

윗 코드는 리눅스 터미널까지 내려가 리눅스 명령어를 내리는 코드이다.


해당 코드를 쓰면 인터넷의 사용 가능 상태를 체크 할 수 있다.


하지만, 문제점은 생각보다 느렸다.


백그라운드 스레드로 사용한다면 상관없겠지만,


만약 인터넷 사용 가능 상태를 미리 조사를 해야 하는 상황이라면, 


사용자가 느끼기엔 느릴 것이다.


느린 원인은 잘 모르겠다. 추측성으론, 


안드로이드 명령어가 아닌 시스템 명령어를 내리는 것이므로 오버헤드가 발생한것이 아닌가 생각한다.


대안책으로 아래의 코드를 보자.



2. HttpURLConnection 객체 이용하기.


 HttpURLConnection객체를 응용해서 인터넷 사용 가능 상태를 검사할 것이다.


 문제점은 HttpURLConnection이 연결되는지 안되는지 기준은 HTTP_OK 상태 이므로,


 만약 인증받지 않은 와이파이 일지라도 HttpURLConnection은 연결상태로 뜰 것이다.


 따라서 아래의 방법으로 해결한다.



HTTP_OK 상태로만 연결 상태를 체크하지 말고, HTTP_OK 상태 체크 후,


웹페이지의 원하는 내용을 긁어올 수 는지 여부로 체크하기.



 무슨 말 이냐면, 인터넷 사용 가능한 권한을 받지 못하는 경우도 connect 는 되지만 원하는 웹페에지의 내용은 안긁어와진다.


 하지만 인터넷 사용 가능한 환경이 주어진다면, connect는 당연히 되고, 원하는 웹페이지의 내용도 긁어올수 있을것이다.


아래의 URL은 위의 논리를 구현하기 위해 구글이 제공하는 URL이다.


http://clients3.google.com/generate_204


해당 URL은 아무내용도 없는 즉, 인터넷이 연결된 상태에서 긁어올 내용을 넣지 않았다.


해당 URL은 HTTP_OK 를 넘어서, HTTP_NO_CONTENT ( 204번 ) 을 리턴한다.


이 URL을 사용 할 시, wifi 가 연결되었지만 권한이 없는경우는 200번을 리턴하고,


권한이 있는경우는 204번을 리턴한다.



아래의 코드를 보자.



멤버변수로 아래의 코드를 추가한다.


public static final String CONNECTION_CONFIRM_CLIENT_URL = "http://clients3.google.com/generate_204";

HttpURLConnection 객체를 호출하는 코드도 추가한다.


private static class CheckConnect extends Thread{
private boolean success;
private String host;

public CheckConnect(String host){
this.host = host;
}

@Override
public void run() {

HttpURLConnection conn = null;
try {
conn = (HttpURLConnection)new URL(host).openConnection();
conn.setRequestProperty("User-Agent","Android");
conn.setConnectTimeout(1000);
conn.connect();
int responseCode = conn.getResponseCode();
if(responseCode == 204) success = true;
else success = false;
}
catch (Exception e) {
e.printStackTrace();
success = false;
}
if(conn != null){
conn.disconnect();
}
}

public boolean isSuccess(){
return success;
}

}

이제 인터넷 상태를 체크해주는 메서드도 만들어본다.


public static boolean isOnline() {
CheckConnect cc = new CheckConnect(CONNECTION_CONFIRM_CLIENT_URL);
cc.start();
try{
cc.join();
return cc.isSuccess();
}catch (Exception e){
e.printStackTrace();
}
return false;
}

필자는 인터넷 환경체크를 우선시 하므로 


스레드에 join을 걸어 mainThread의 작업이랑 동기화 하였다.


만약 서브스레드로만 돌려도 되는 상황이라면 join을 지우면 되겠다.




이제 isOnline( ) 메서드와 getWhatKindOfNetwork( ) 메서드만 사용한다면,


인터넷 상태를 완벽하게 체크 할 수 있다.





 


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘은 안드로이드 최신 위젯에 속하는


플로팅액션버튼(FloatingActionButton)에 대해 포스팅 한다.


먼저 아래의 유알엘(안드로이드 디벨로퍼) 에서 발췌한 


플로팅액션버튼에 관한 내용이다.


https://developer.android.com/reference/android/support/design/widget/FloatingActionButton.html


Floating action buttons are used for a special type of promoted action. They are distinguished by a circled icon floating above the UI and have special motion behaviors related to morphing, launching, and the transferring anchor point.

Floating action buttons come in two sizes: the default and the mini. The size can be controlled with the fabSize attribute.

As this class descends from ImageView, you can control the icon which is displayed via setImageDrawable(Drawable).

The background color of this view defaults to the your theme's colorAccent. If you wish to change this at runtime then you can do so viasetBackgroundTintList(ColorStateList).



간단하게 해석해보자면, 


플로팅액션버튼은 다른 View 들과 구별되게끔 공중에 떠있는듯한(그림자 효과를 이용- 엘리베이션 효과) UI이며,


또한 이버튼은 특정 지점으로 부터 애니메이션적 특징을 가질 수 있다고 한다.


xml 속성인 fabSize를 이용하여 크기를 정할 수 있고, 크기를 정하지 않을시 default 사이즈로 셋팅이 된다.


이미지뷰(ImageView) 위젯을 상속 받았기 때문에 이미지뷰 와 똑같이 이미지를 셋팅할 수 있고,


버튼의 색상은 theme 색깔인 colorAccent 기준으로 정해진다. 만약 코드로 바꾸고 싶다면,


setBackgroundTintList( 컬러값 ) 으로 바꿀 수 있다.



해당 위젯을 쓰기위해선 그래들에 아래와 같이 적어 sync 해야한다.


compile 'com.android.support:design:22.2.0'



이제, 코드를 통해 사용해보자. 중요한 점은 플로팅액션버튼의 에니메이션 특징이라던가


다른 View와 적절하게 이용되는 것들은( ex) snackBar가 나타날때 플로팅액션버튼이 스낵바위로 올라가는 애니메이션 발동,


리사이클러뷰를 스크롤할때 플로팅액션버튼이 숨겨지는 애니메이션 발동등)


CoordinatorLayout이랑 같이 사용해야 한다는 점이다. CoordinatorLayout이 여러 위젯,View들을 통솔하고 관리하는 역할을 하기 때문이다.


CoordinatorLayout 과 같이 사용할시 anchor (특정지점) 을 정할 수 있는것도 특징이다. 아래의 예시를 보자.




acitvity_mainxml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>

</android.support.design.widget.AppBarLayout>

<FrameLayout
android:id="@+id/frameLayout"
android:background="#ffff0000"
android:layout_width="match_parent"
android:layout_height="300dp"/>

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

위와 같이 코딩 했을 경우 FrameLayout(빨간배경)과 상관없이 FloatingActionButton의 위치는 아래의 그림과 같이 배치된다.




위에서 말한 anchor 속성을 적용해 FrameLayout(빨간배경) 에 위치시켜보자.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>

</android.support.design.widget.AppBarLayout>

<FrameLayout
android:id="@+id/frameLayout"
android:background="#ffff0000"
android:layout_width="match_parent"
android:layout_height="300dp"/>

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"
app:layout_anchor="@id/frameLayout"
app:layout_anchorGravity="right|bottom"/>

</android.support.design.widget.CoordinatorLayout>

위와 같이 FloatingActionButton 안에 app:layout_anchor="~" , app:layout_anchorGravity="~" 를 추가했다.


해당 속성은 CoordinatorLayout이 가지고 있다. 특정지점을 frameLayout이란 id값을 통해 알려주고,


레이아웃 그래비티를 적절히 사용해 배치시켜 봤다. 아래의 그림이 결과물이다.



중요한점은 플로팅액션버튼, 즉 자기자신의 위젯의 그래비티와는 무관하다는 점이다. 


만약 위젯 레이아웃 즉, layout_gravity 속성을 적을시 그에 맞게 anchor 지점에서 이동한다.


anchor 지점을 정하는것은 CoordinatorLayout이 담당하는 것이므로, 당연히 다른 위젯들도 anchor를 정할 수 있다.


또한 anchor 을 지정해줌으로써, 특정 애니메이션의 행위를 보장 해줄 수 도 있다.


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅은 많이 쓰이는 View 중 하나인


WebView에 대해서다.


인터넷을 볼 수 있는 브라우저를


안드로이드 view 형태로 본다고 생각하면 된다.


간단하게 쓰는법에 대해 알아볼 것 이다.


View 공간을 먼저 정해야 하므로 아래와 같이 Xml을 코딩한다.


notice_webView_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingLeft="50dp"
android:paddingRight="50dp"
android:paddingTop="100dp"
android:paddingBottom="100dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

FrameLayout의 패딩값으로 webView의 크기를 정했다.


아래는 java 코딩이다.


MainActivity.java

WebView webView = findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("http://www.naver.com");

간단한 코딩으로 네이버를 열어볼 수 있다.



포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

안드로이드 SQLite 2편이다.


1편은 아래의 링크로 가면 된다.


http://mommoo.tistory.com/20


1편에서는 SQLite를 쓰기위한 준비사항 과 개념에


관하여 다루었다.


이번 포스팅은 직접 사용 해볼 것이다.


내가 필요한 Column은 id와 pass 값이다.


테이블의 이름은 Info 로 할것이다.


해당 테이블을 만들기 위해


아래와 같이 코딩한다.


public class MommooDB extends SQLiteOpenHelper {
public MommooDB(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "CREATE TABLE Info"+"(id Text primary key, pass Text);";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
onCreate(db);
}
}


Info라는 db파일을 처음 접근할때 onCreate 메서드가 실행되고,


그 결과로 Info 테이블이 생성 될 것이다.


해당 쿼리문은 id 와 pass 를 문자열로 저장하고 id를 primary key로 지정하는 쿼리문이다.


사용하는 방법은 아래와 같다. 간단하게 insert 와 select를 해볼 것 이다.


ex ) insert



MommooDB mommooDB = new MommooDB(this,"Info",null,1);
SQLiteDatabase database = mommooDB.getWritableDatabase();
String id = "Mommoo";
String pass = "mm";
String sql = "insert into Info values('"+id+"','"+pass+"')";
database.execSQL(sql);



혹시, 버전을 바꾸고 싶으면 생성자안 4번째 인자만 바꿔주면 된다. ( 버전은 1이상만 가능)


ex) select


select를 할때는 일반적으로 아래와 같이 커서 클래스를 쓴다. 


MommooDB mommooDB = new MommooDB(this,"Info",null,1);
SQLiteDatabase database = mommooDB.getReadableDatabase();
String sql = "select * from Info where id='Mommoo'";
Cursor cursor = database.rawQuery(sql,null);
while(cursor.moveToNext()){
String id = cursor.getString(0);
String pass = cursor.getString(1);
}


cursor.moveToNext() 메서드를 통해, 테이블의 튜플 갯수만큼 루프를 돈다.


상황에 맞게 코딩하면 되겠다.





insert문은 mommooDB.getWritableDatabase(); 를 사용했고 select 문은 mommooDB.getReadableDatabase(); 를 사용했는데,


메서드명 에서도 알 수 있드시, 쓰기모드 읽기모드 이다. insert는 db값을 입력시켜야 하므로 쓰기모드를 사용했고,


select는 디비 튜플값만 불러오면 되므로 읽기모드를 사용한것이다.


위와같이 데이터베이스를 사요한 후 에는 꼭 아래와 같이 close를 해준다.



mommooDB.close();
database.close();
cursor.close();



포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

안드로이드는 기존에 자바에서 제공하는 Thread 뿐만아니라


AsyncTask라는 스레드 객체를 제공한다.


안드로이드가 AsyncTask 라는 객체를 왜 만들어 제공할까?


아래의 글은 Android Developer 에서 발췌한 일부 글이다.


AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as ExecutorThreadPoolExecutor and FutureTask.

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread.



요약하자면, AsyncTask는 백그라운드 스레드 와 UI 스레드를 같이 쓰기 쉽게 설계했으며, 


일반 스레드와 달리, 간단한 작업에 적합하게 만들었다고 설명되어 있다.


백그라운드 스레드와 UI 스레드를 같이 쓰기 쉽다는 말이 무슨 뜻일까?


아래의 설명을 보자.



안드로이드에서의 일처리는 메인스레드(UI 스레드)가 담당한다. 특히 UI와 관련된( ex) TextView,ImageView )


일처리는 메인스레드만 담당 하게끔 설계를 했다. 그래서 메인스레드를 UI스레드라고도 불린다.



따라서 복잡한 계산은 백그라운드 스레드( 메인 스레드가 아닌 다른 스레드의 총칭)에 맡긴후 


계산된 결과값을 UI스레드에게 일을 시켜야 하는 것이다.


그래서 AysncTask 라는 객체를 만들었고 위에서 설명한 일들을 쉽게 구현 할 수 있도록 메서드를 제공한다.


아래의 AysncTask 예시를 보자.

import android.os.AsyncTask;

public class MommooAsyncTask extends AsyncTask<String,Void,String>{

public String result;

@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected String doInBackground(String... params) {
return result;
}

@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}

@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
}


AsyncTask객체는 abstract로 작성되었다. 따라서, 익명클래스로 사용허던가 위와 같이 상속을 통해서 사용해야 한다.


public class MommooAsyncTask extends AsyncTask<String,Void,String>{


제네릭 인자3개를 정해야한다. 


첫번째 인자는 doInBackground 메서드에 선언하는 가변인수 매개변수의 타입을 정한다.


두번째 인자는 onProgressUpdate 메서드에 선언하는 가변인수 매개변수의 타입을 정한다.


세번째 인자는 onPostExecute 메서드에 선언하는 매개변수의 타입을 정한다.


@Override
protected void onPreExecute() {
super.onPreExecute();
}


첫번째 메서드다. 해당 메서드는 이름에서 볼 수 있드시,  background스레드를 실행하기전 준비 단계이다.


변수의 초기화나, 네트워크 통신전 셋팅해야할 것들을 위의 메서드 공간에 작성한다. 



@Override
protected String doInBackground(String... params) {
return result;
}


두번째 메서드다. 해당 메서드가 background 스레드로 일처리를 해주는 곳이다.


보통 네트워크, 병행 일처리등을 위 메서드 공간에 작성한다.


중요한건 마찬가지로 스레드 이므로 UI스레드가 어떤 일을 하고 있는지 상관없이


별개의 일을 진행한다는 점이다. 따라서 AysncTask는 비동기적으로 작동한다.



@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}


세번째 메서드는 doInBackground 메서드에서 중간중간에 UI스레드 에게 일처리를 맡겨야 하는 상황일때


쓴다. 매개변수로 Void를 받으므로, doInBackground안에 실제인자가 없이,


 publishProgress( ) 메서드를 호출하면 BackgroundThread 중간에 mainThread에게 일을 시킬 수 있다.



@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}


마지막 메서드다. background Thread가 일을 끝마치고 리턴값으로 result를 넘겨준다.


그 값을 지금 보고 있는 해당 메서드가 매개변수로 받은후 받은 데이터를 토데로


UI스레드에 일처리를 시킬때 쓰는 메서드이다.


보통 UI변경 ( ex) textview.setText("~~") )할때 많이 사용된다. 왜냐면


위에서도 말했다시피 UI변경은 메인스레드가 아닌 다른 스레드에서의 변경은 막았기 때문이다.


위에서 만든 AysncTask를 사용 하는방법은 아래와 같이 하면된다.


MommooAsyncTask asyncTask = new MommooAsyncTask();
asyncTask.execute();


execute의 아규먼트는 doInBackground에서 받는 String... 가변인수이다. 필요시 넣으면 되겠다.



해당 URL은 저번에 작성한 포스팅인데 AsyncTask를 사용하였다. 예시로 보면 될 것이다.


http://mommoo.tistory.com/5



여기까지가 준비한 AysncTask의 내용이다.


직접 Thread와 Handler 를 구현해보고 작성해봤다면,


안드로이드가 제공해주는 AysncTask가 얼마나 편리한지 알 수 있을 것이다.



포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅할 내용은 Toolbar 위젯이다.


Toolbar 위젯이 뭔지 아래의 설명을 보자.


해당 내용은 안드로이드 디벨로퍼 API문서이다.




A standard toolbar for use within application content.

A Toolbar is a generalization of action bars for use within application layouts. While an action bar is traditionally part of an Activity's opaque window decor controlled by the framework, a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy. An application may choose to designate a Toolbar as the action bar for an Activity using the setActionBar() method.



요약하자면, 어플리케이션의 제어를 도와주는 액션바는 스크린의 최상위 뷰인 윈도우 decor에 붙여져 있어 다루기 어렵다.

하지만 toolbar는 어느 뷰에서나 종속관계 상관없이 다채롭게 사용가능하며 actionbar 사용하듯 toolbar를 사용할 수 있다는 내용이다.

머터리얼 디자인이 등장함에 따라 UI의 큰 변화가 있었다. ActionBar를 사용시 구현하기 어려운 경우가 많아 

toolbar란 위젯을 만든거 같다. 자세한 내용을 보고 싶다면 아래의 링크를 참조하길 바란다.

http://developer.android.com/intl/ko/reference/android/widget/Toolbar.html 




해당 위젯은 롤리팝 부터 나온 위젯이기 때문에(API level 21) 만약 하위 os에서 쓰고 싶다면 서포트 라이브러리가 필요하다.


build.gradle에서 dependencies에 아래와 같이 추가해준다.

dependencies {

compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:design:23.2.1'

}


actionBar 대신에 toolbar를 쓸 것 이므로, 액션바가 없는 테마로 변경해야한다.


values 폴더에 styles.xml 안에다가 해당 테마를 만들어준다.


<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>



Activity에 붙일 레이아웃을 아래와 같이 작성한다. (main_activity.xml)


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppTheme.NoActionBar">
<android.support.v7.widget.Toolbar
xmlns:app="http://schemas.android.com/apk/res-auto"
app:titleTextColor="#fff"
android:id="@+id/toolbar"
android:background="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
</android.support.v7.widget.Toolbar>
</LinearLayout>


레이아웃을 작성후 액티비티의 onCreate 메서드에 아래와 같이 작성한다.


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setTitleTextColor(Color.WHITE);
}

아래의 이미지는 결과물이다.








포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee




오늘은 HttpURLConnection 에 대해 포스팅 해보겠다. 


포스팅 하게 된 이유는 저번에 잘 사용하던 httpClient(아파치 라이브러리)가 deprecated 되었기 때문이다.


갑자기 사라져서 당황했었다... 아래 내용은 Google Developer 에서 발췌한 android 6.0 변화에서 httpClient에 대한 이야기다. 


Apache HTTP Client Removal


Android 6.0 release removes support for the Apache HTTP client. If your app is using this client and targets Android 2.3 (API level 9) or higher, use the HttpURLConnection class instead. This API is more efficient because it reduces network use through transparent compression and response caching, and minimizes power consumption. To continue using the Apache HTTP APIs, you must first declare the following compile-time dependency in your build.gradle file:

android {
    useLibrary
'org.apache.http.legacy'
}


요약하자면, 위와 같이 gradle에 해당 라이브러리를 추가하면 httpClient 쓸수는 있다. 또 말하기를, httpClient의 기능은 많지만 무겁다, 


그리고 안드로이드에서는 조금 오바(?)스러운 httpClient보다는,


안드로이드에 적합한 API인 HttpURLConnection을 사용하라는 이야기다.(해당 이슈는 예전부터 나오던 이슈긴 하다.)


뭐.. httpClient가 편리하지만 저렇게 권장하지 않는 API를 쓰기에는 유지보수와 기능에 문제가 있다고 판단, 바로 HttpURLConnection으로 옮겼었다.


서론은 여기까지 하고, 시작해 보자. HttpURLConnection도 굉장히 쉽다.


먼저 인터넷 환경이 필요한 작업이므로 AndroidManifest.xml 파일에 


<uses-permission android:name="android.permission.INTERNET"/> 을 작성해 인터넷 환경을 만들어준다.


이제 MainActivity에 네이버의 웹 정보를 받아오는 코드를 작성해보자.


new AsyncTask<Void,Void,Void>(){
@Override
protected void onPreExecute() {
super.onPreExecute();
strUrl = "http://www.naver.com"; //탐색하고 싶은 URL이다.
}

@Override
protected Void doInBackground(Void... voids) {
try{
Url = new URL(strUrl); // URL화 한다.
HttpURLConnection conn = (HttpURLConnection) Url.openConnection(); // URL을 연결한 객체 생성.
conn.setRequestMethod("GET"); // get방식 통신
conn.setDoOutput(true); // 쓰기모드 지정
conn.setDoInput(true); // 읽기모드 지정
conn.setUseCaches(false); // 캐싱데이터를 받을지 안받을지
conn.setDefaultUseCaches(false); // 캐싱데이터 디폴트 값 설정

strCookie = conn.getHeaderField("Set-Cookie"); //쿠키데이터 보관

InputStream is = conn.getInputStream(); //input스트림 개방

StringBuilder builder = new StringBuilder(); //문자열을 담기 위한 객체
BufferedReader reader = new BufferedReader(new InputStreamReader(is,"UTF-8")); //문자열 셋 세팅
String line;

while ((line = reader.readLine()) != null) {
builder.append(line+ "\n");
}

result = builder.toString();

}catch(MalformedURLException | ProtocolException exception) {
exception.printStackTrace();
}catch(IOException io){
io.printStackTrace();
}
return null;
}

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
System.out.println(result);
}
}.execute();

AsynTask는 통신을 할 때 필요한 클래스이다. 이해하면 사용하기 쉽지만, 모르는 사람은 일단 따라 써보기로 하자.


protected void onPreExecute() 메서드 안에는 통신을 하기전 필요한 정보를 셋팅하는 구간이다.


우리가 탐색할 네이버 주소를 적었다.


protected Void doInBackground(Void... voids) 이 메서드가 실제로 통신 할때 작동하는 부분이다.


왠만한 설명은 주석처리 해놨다. 좀더, 정리를 해주자면..


Url = new URL(strUrl); // URL화 한다.
HttpURLConnection conn = (HttpURLConnection) Url.openConnection(); // URL을 연결한 객체 생성.


이 부분이 해당 URL(예시는 네이버)의 정보를 담고 있는 객체를 생성하는 부분이다.


conn.setRequestMethod("GET"); // get방식 통신
conn.setDoOutput(true); // 쓰기모드 지정
conn.setDoInput(true); // 읽기모드 지정
conn.setUseCaches(false); // 캐싱데이터를 받을지 안받을지
conn.setDefaultUseCaches(false); // 캐싱데이터 디폴트 값 설정


이 부분이 해당 통신을 위한 셋팅 부분이다. HttpURLConnection은 스트림 기반이라 셋팅 할 것이 많다.


strCookie = conn.getHeaderField("Set-Cookie"); //쿠키데이터 보관

이 부분은 세션지정을 위한 쿠키값 저장을 하는 부분이다.


httpClient는 쿠키값을 알아서 저장해줬지만 HttpURLConnection은 따로 저장 관리를 해줘야한다.


InputStream is = conn.getInputStream(); //input스트림 개방

StringBuilder builder = new StringBuilder(); //문자열을 담기 위한 객체
BufferedReader reader = new BufferedReader(new InputStreamReader(is,"UTF-8")); //문자열 셋 세팅
String line;

while ((line = reader.readLine()) != null) {
    builder.append(line+ "\n");
}

 이 부분은 웹 정보를 파싱하는 부분이다. 중요한 점은 웹 정보 마다 다른 문자열 셋일 수 있으므로 맞는 문자열 셋팅을 해줘야 한다.


protected void onPostExecute(Void aVoid)

이 메서드 부분이 해당 통신이 끝난 후 통신작업이 아닌 마무리 작업(UI작업, 사용자 정의 작업 등)을 하는 부분이다.


예시에서는 콘솔에 출력하는 메서드를 썻다.


실행해보면 네이버의 정보를 콘솔에 출력하는 것을 볼 수 있다.


oRTK : { "t": "2015-11-17T12:25Z", "s": "2015.11.17. (화) 12:25 PM", "d": [ { "k": "오마이비너스", "c": "up", "a": "상승", "n": "72" }, 

{ "k": "신민아", "c": "up", "a": "상승", "n": "99" }, { "k": "진세연", "c": "up", "a": "상승", "n": "225" }, 

{ "k": "이정재", "c": "up", "a": "상승", "n": "216" }, { "k": "육룡이 나르샤 결방", "c": "up", "a": "상승", "n": "174" }, 

{ "k": "다이나믹듀오", "c": "up", "a": "상승", "n": "216" }, { "k": "풍선껌", "c": "up", "a": "상승", "n": "63" }, 

{ "k": "첼시 리", "c": "up", "a": "상승", "n": "348" }, { "k": "먹는존재", "c": "up", "a": "상승", "n": "150" }, 

{ "k": "명지전문대", "c": "up", "a": "상승", "n": "93" } ] },


콘솔에 찍힌 정보 중간에 실시간 검색어 정보를 발견했다. (실시간 정보는 네이버에서 제공하는 API가 있다.)


시간대가 틀려 찍히는 정보가 틀리겠지만... 무튼 성공 한걸 알 수 있다.


해당 예시는 get 방식으로 웹 정보를 받아오는 방식이다.


추후에 post방식으로 웹 정보를 이용하는 방법도 포스팅 하겠다.



해당 프로젝트는 https://github.com/Mommoo/ExampleCode/tree/master/HttpURLConnection 에서 받을 수 있다.






포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee




http://mommoo.tistory.com/1 ( RecyclerView , CardView 사용하기.(1) )




2편이다. 처음 읽는 독자는 위의 경로의 포스팅을 먼저 읽기를 권한다.


RecyclerView에 필요한 Adpater를 선언할 것이다.

참조한 구글 문서는 단순히 TextView만을 이용한 예제이다.


본 게시물은 추가적으로 image를 넣어 image와 text로 구성된 예제임을 밝힌다.



MyAdapter.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private ArrayList<MyData> mDataset;
 
    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder
    public static class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case 
        public ImageView mImageView;
        public TextView mTextView;
 
        public ViewHolder(View view) {
            super(view);
            mImageView = (ImageView)view.findViewById(R.id.image);
            mTextView = (TextView)view.findViewById(R.id.textview);
        }
    }
 
    // Provide a suitable constructor (depends on the kind of dataset)
    public MyAdapter(ArrayList<MyData> myDataset) {
        mDataset = myDataset;
    }
 
    // Create new views (invoked by the layout manager)
    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        // create a new view
        View v = LayoutInflater.from(parent.getContext())
                               .inflate(R.layout.my_view, parent, false);
        // set the view's size, margins, paddings and layout parameters
        ...
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }
 
    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        holder.mTextView.setText(mDataset.get(position).text);
        holder.mImageView.setImageResource(mDataset.get(position).img);
    }
 
    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return mDataset.size();
    }
}
 
class MyData{
    public String text;
    public int img;
    public MyData(String text, int img){
        this.text = text;
        this.img = img;
    }
}
cs



MyAdpater.class를 선언하고, RecyclerView.Adapter를 상속 받았다.


MyAdpater안에 직접 구현한 ViewHolder 클래스를 제네릭으로 받는 구조이다.  물론 직접 만드는 ViewHolder도 


ReclerView.ViewHolder를 상속받아야 한다.


(ViewHolder는 ListView를 사용할 당시, RecylerView처럼 View를 효율적으로 쓰기 위한 디자인 패턴중 하나였다. 

리소스를 제법 잡어먹는 findViewById()를 적게 호출하기 위함이다. RecyclerView 에서는 적극적으로 ViewHolder 패턴을 사용하라고 권장한다. )


ViewHolder 클래스는 RecyclerView의 item에 들어갈 view를 받은후 그 view 안에 있는 ImageView와 TextView를 초기화 한다.


그다음은 MyAdapter 생성자가 보이는데, 매개변수로 ArrayList<MyData> 객체를 받는다. MyData 클래스는 data-set을 위한 클래스로


해당 예제에 맨 아래에 생성했다.




중요한 내용은 지금부터이다.


onCreateViewHolder 메서드안의 내용을 잘 바꾸어야 내가 원하는 결과물이 나온다.


첫번째로, View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_view, parent, false); 는


앞서 말한 ViewHolder에 넣어줄 view를 찾는 과정이다. my_view라는 xml에는 cardview가 들어갈 예정이다.


이 view를 넣기위한 ViewHolder를 다음과 같이 선언한 후 넣는다.


 ViewHolder vh = new ViewHolder(v);


그다음 onBindViewHolder 메서드를 보자. 이 메서드는 RecyclerView의 item의 셋팅과 사용자가 스크롤링 할때, 호출되는 메서드이다.


우리가 원하는 데이터가 포지션별로 ArrayList<MyData>에 저장되어 있다. 이러한 데이터를 포지션별로 보여주는 것을 보장해준다.


마지막으로 MyData 클래스이다. 우리가 원하는 image와 text 데이터를 보관하기 위한 데이터 저장소 개념으로 보면 된다.



다음은 위에서 소개한 my_view.xml을 코딩 해보자.



my_view.xml


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.CardView
        android:id="@+id/cardview"
        card:cardCornerRadius="3dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
        <ImageView
            android:scaleType="fitXY"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:id="@+id/image"/>
        <TextView
            android:id="@+id/textview"
            android:gravity="center|left"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"/>
        </LinearLayout>
    </android.support.v7.widget.CardView>
</RelativeLayout>
cs



위의 CardView 위젯을 보면 android: 대신 card: 인자가 보이는데, 


이 인자는 부모뷰에 xmlns:card="http://schemas.android.com/apk/res-auto"


등록을 해주면 된다. cardCornerRadius 값은 모서리 둥근 정도를 결정한다.


해당 카드뷰 view 아래는 데이터를 보여줄(image,text) 하위 view들을 지정했다.


cardview 자체는 FrameLayout를 상속 받기 때문에 따로 LinearLayout을 선언하여 수직 정렬 하였다.


다 끝났다. 이제 MyActivity로 돌아가 데이터를 넣어본다.



MyActivity.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MyActivity extends Activity {
    private RecyclerView mRecyclerView;
    private RecyclerView.Adapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;
    private ArrayList<MyData> myDataset;    
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity);
        mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
 
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);
 
        // use a linear layout manager
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);
 
        // specify an adapter (see also next example)
        myDataset = new ArrayList<>();
        mAdapter = new MyAdapter(myDataset);
        mRecyclerView.setAdapter(mAdapter);
        
        myDataset.add(new MyData("#InsideOut", R.mipmap.insideout));
        myDataset.add(new MyData("#Mini", R.mipmap.mini));
        myDataset.add(new MyData("#ToyStroy", R.mipmap.toystory));
 
    }
    ...
}
cs


컴파일을 해보면...




멋진 카드효과와 함께 잘 나온다.


해당 프로젝트는 https://github.com/Mommoo/ExampleCode/tree/master/RecyclerView  에서 받을 수 있다.




포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

+ Recent posts