헬로, 안드로이드 10 주차 연결된세상 강대기동서대학교컴퓨터정보공학부
학습목표 인텐트로다른액티비티나프로그램을실행시킬수있다. 웹뷰를통해웹화면을액티비티화면의일부로구성할수있다. 자바스크립트를통해안드로이드프로그램을호출하는방법을안다. 안드로이드응용프로그램에서웹서비스를이용하는방법을안다.
차례 인텐트로브라우징하기 뷰안의웹 자바스크립트에서자바로, 자바에서자바스크립트로 웹서비스이용하기 요약 퀴즈 연습문제
인텐트로브라우징하기 휴대폰은더이상통화용도로만사용되지않음 안드로이드 WebKit 을통한웹브라우징과 TCP/IP 표준소켓 네개의예제 브라우저인텐트 (browser intent) 브라우저뷰 (browser view) 로컬브라우저 (local browser) 구글번역프로그램 (translate) 데이터바인딩과스레딩및웹서비스를사용
브라우저인텐트 (Browser Intent) 프로젝트생성 BrowserIntent org.example.browserintent BrowserIntent BrowserIntent main.xml 에서 EditText 와 Button 위젯지정 strings.xml 에필요한스트링지정 BrowserIntent 의 oncreate() 메서드에서 EditText 와 Button 위젯에대한 listener 코드안에서 openbrowser() 함수호출 openbrowser() 함수는 Intent 로안드로이드안의기본브라우저호출
main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <EditText android:id="@+id/url_field" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0" android:lines="1" android:inputtype="texturi" android:imeoptions="actiongo" /> <Button android:id="@+id/go_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/go_button" /> </LinearLayout>
strings.xml <resources> <string name="app_name">browserintent</string> <string name="go_button">go</string> </resources>
BrowserIntent.java package org.example.browserintent; public class BrowserIntent extends Activity { private EditText urltext; private Button gobutton; @Override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // Get a handle to all user interface elements urltext = (EditText) findviewbyid(r.id.url_field); gobutton = (Button) findviewbyid(r.id.go_button); // Setup event handlers gobutton.setonclicklistener(new OnClickListener() { public void onclick(view view) { openbrowser(); ); urltext.setonkeylistener(new OnKeyListener() { public boolean onkey(view view, int keycode, KeyEvent event) { if (keycode == KeyEvent.KEYCODE_ENTER) { openbrowser(); return true; return false; ); /** Open a browser on the URL specified in the text box */ private void openbrowser() { Uri uri = Uri.parse(urlText.getText().toString()); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startactivity(intent);
브라우저인텐트실행예
뷰안의웹 (WebView) 단순한텍스트라도웹브라우저로디스플레이하는게좋음 프로젝트생성 BrowserView org.example.browserview BrowserView BrowserView main.xml 에는 WebView 추가 BrowserView 의 oncreate() 메서드는 WebView 의 listener 코드추가 openbrowser() 함수는 WebView.loadUrl() 호출 AndroidManifest.xml 에퍼미션추가
main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/url_field" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0" android:lines="1" /> <Button android:id="@+id/go_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/go_button" /> </LinearLayout> <WebView android:id="@+id/web_view" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1.0" /> </LinearLayout>
BrowserView.java package org.example.browserview;... public class BrowserView extends Activity { private EditText urltext; private Button gobutton; private WebView webview; @Override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // Get a handle to all user interface elements urltext = (EditText) findviewbyid(r.id.url_field); gobutton = (Button) findviewbyid(r.id.go_button); webview = (WebView) findviewbyid(r.id.web_view); // Setup event handlers gobutton.setonclicklistener(new OnClickListener() { public void onclick(view view) { openbrowser(); ); urltext.setonkeylistener(new OnKeyListener() { public boolean onkey(view view, int keycode, KeyEvent event) { if (keycode == KeyEvent.KEYCODE_ENTER) { openbrowser(); return true; return false; ); /** Open a browser on the URL specified in the text box */ private void openbrowser() { webview.loadurl(urltext.gettext().tostring()); webview.requestfocus();
AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.example.browserview" android:versioncode="1" android:versionname="1.0.0"> <uses-permission android:name="android.permission.internet" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".browserview" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest>
뷰안의웹실행예
WebView 메서드전체목록 addjavascriptinterface() 자바스크립트의자바객체액세스허용 createsnapshot() 현페이지의스크린샷생성 getsettings() 설정을조정하는 WebSettings 객체반환 loaddata() 브라우저에주어진문자열데이터로딩 loaddatawithbaseurl() 기준 URL 을사용해주어진데이터로딩 loadurl() 주어진 URL 을사용해웹페이지로딩 setdownloadlistener() 사용자가.zip 이나.apk 파일을다운로드하는것과같은다운로드이벤트의콜백등록 setwebchromeclient() 제목또는진행줄표시줄을업데이트하거나자바스크립트대화상자를여는등의, WebView 영역밖에서실행되는이벤트의콜백등록 setwebviewclient() 리소스로드하기, 키누르기, 인증요청등의이벤트를방해하도록애플리케이션이브라우저에고리를설정함 stoploading() 현재페이지로딩멈추기
자바스크립트와자바 (on 안드로이드 ) 자바스크립트를통한메서드호출 임베딩된웹뷰를통해웹페이지에서안드로이드의기능에접근하는것 WebView 클래스안의 addjavascriptinterface() 메서드를통해, 임베딩된브라우저내에서 DOM 을확장하고자바스크립트가접근할새객체를정의할수있음 à 자바스크립트코드가객체에메서드를호출하면안드로이드프로그램안의메서드가호출됨 역으로, 안드로이드프로그램에서자바스크립트메서드호출도가능함 loadurl() 을호출하고 javascript:code-to-execute 형식으로 URL 을전달함 브라우저는새페이지가아닌현재페이지에서자바스크립트를실행시킴 본방식은 ASP, ASP.NET 등의프로그래밍에서도그대로사용됨 보안문제를신중히고려해야함
자바스크립트에서자바호출 프로젝트생성 LocalBrowser / org.example.localbrowser / LocalBrowser / LocalBrowser main.xml index.html res 가아닌 asset 디렉토리안에들어감. 컴파일된리소스가아니기때문임. asset 의모든파일들은프로그램이설치될때, 로컬스토리지에그대로복사됨 calljs() 함수를통해안드로이드프로그램호출 첫번째링크는 window.alert() 함수부른후짧은메시지출력 두번째링크는 window.android 객체위에 callandroid() 메서드호출 여기서 window.android 는정의되어있지않으며, 애플리케이션이브라우저를임베딩해서쓸때, 객체를직접정의해페이지가사용할수있게함
자바스크립트에서자바호출 LocalBrowser.java Handler 를통해자바스크립트를위한브라우저용의특별한스레드와안드로이드사용자인터페이스호출을위한메인스레드간의전환을다룸 AndroidBridge는자바스크립트에노출된객체로자바코드를호출하게해줌. 자바스크립트가 callandroid() 를호출하면, 애플리케이션은새로운 Runnable 객체를만들어 Handler.post() 를통해메인스레드의실행중인큐에집어넣음. 나중에메인스레드는기회가되면 run() 메서드를호출하고이것은 settext() 를호출해 TextView 객체내의텍스트를변경함 oncreate() 메서드안에서자바스크립트를활성화하고 AndroidBridge를자바스크립트에등록함. 임의의 WebChromeClient 객체를만들어 setwebchromeclient() 메서드로등록함. 브라우저가 window.alert() 를사용해자바스크립트경고를여는작업을수행함. onjsalert() 안에 Toast 클래스를이용해서 3초간보이는메시지창을만듦 여기서일반적으로크롬은브라우저창주위를정리하는모든작업을의미함 loadurl() 을통해로컬웹페이지를로드함. file:/// 로 / 를 3개사용함. 자바에서자바스크립트로의호출을위해버튼클릭리스너설정. 버튼이눌리면 onclick() 이호출되고, 이메서드는 WebView.loadUrl() 을호출하여브라우저에서확인될자바스크립트코드가전달됨. 이코드는 index.html에정의된 calljs() 함수호출.
main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <WebView android:id="@+id/web_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" /> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" android:padding="5sp"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textsize="24sp" android:text="textview" /> <Button android:id="@+id/button" android:text="@string/call_javascript_from_android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textsize="18sp" /> <TextView android:id="@+id/text_view" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textsize="18sp" /> </LinearLayout> </LinearLayout>
index.html <html> <head> <script language="javascript"> function calljs(arg) { document.getelementbyid('replaceme').innerhtml = arg; </script> </head> <body> <h1>webview</h1> <p> <a href="#" onclick="window.alert('alert from JavaScript')"> Display JavaScript alert</a> </p> <p> <a href="#" onclick="window.android.callandroid('hello from Browser')"> Call Android from JavaScript</a> </p> <p id="replaceme"> </p> </body> </html>
LocalBrowser.java (1/2) package org.example.localbrowser;... public class LocalBrowser extends Activity { private static final String TAG = "LocalBrowser"; private final Handler handler = new Handler(); private WebView webview; private TextView textview; private Button button; /** Object exposed to JavaScript */ private class AndroidBridge { public void callandroid(final String arg) { // must be final handler.post(new Runnable() { public void run() { ); Log.d(TAG, "callandroid(" + arg + ")"); textview.settext(arg);
LocalBrowser.java (2/2) @Override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // Find the Android controls on the screen webview = (WebView) findviewbyid(r.id.web_view); textview = (TextView) findviewbyid(r.id.text_view); button = (Button) findviewbyid(r.id.button); // Rest of oncreate follows... // Turn on JavaScript in the embedded browser webview.getsettings().setjavascriptenabled(true); // Expose a Java object to JavaScript in the browser webview.addjavascriptinterface(new AndroidBridge(), "android"); // Set up a function to be called when JavaScript tries // to open an alert window webview.setwebchromeclient(new WebChromeClient() { @Override public boolean onjsalert(final WebView view, final String url, final String message, JsResult result) { Log.d(TAG, "onjsalert(" + view + ", " + url + ", " + message + ", " + result + ")"); Toast.makeText(LocalBrowser.this, message, 3000).show(); result.confirm(); return true; // I handled it ); // Load the web page from a local asset webview.loadurl("file:///android_asset/index.html"); // This function will be called when the user presses the // button on the Android side button.setonclicklistener(new OnClickListener() { public void onclick(view view) { Log.d(TAG, "onclick(" + view + ")"); webview.loadurl("javascript:calljs('hello from Android')"); );
자바스크립트와자바실행예
웹서비스이용하기 Blocking I/O 입출력호출을하고결과를기다림 ( 동기호출 ) Non-Blocking I/O 입출력호출을하고결과가나오는동안, 프로그램수행이가능함 ( 비동기호출 ) Non-Blocking I/O 를다루기위한 java.util.concurrent à Java 5 에통합됨 웹서비스를이용한번역프로그램 Translate / org.example.translate / Translate / Translate AndroidManifest.xml 인터넷사용허가 main.xml TableLayout 사용 arrays.xml 스피너를위한스트링배열 strings.xml TranslateTask.java 교재에서설명이생략되어있음
Translate.java findviews() 레이아웃파일에정의된사용자인터페이스구성요소들을연결함 setadapters() 스피너의데이터소스정의. Adapter 클래스는데이터소스와사용자컨트롤을연결함. setlisteners() 사용자인터페이스처리기설정 initthreading() 입력텍스트가바뀔때와번역언어가변할때각각호출되는두개의리스너정의. 텍스트변화는 1 초, 언어변화는 1/5 초 TranslateTask 인스턴스를 ExecutorService 클래스가가지는쓰레드큐에삽입함. Future 클래스인스턴스가반환됨. queueupdate() 호출 Handler 를사용해메인스레드의작업목록에지연된업데이트요청
지연된요청과쓰레드 매번키입력마다요청하면네트워크트래픽낭비인반면, 사용자가입력이끝났음을알리게하는건사용자를귀찮게하고 AJAX 와같은고수준입출력이아님. 따라서, 이를처리하기위한지연되는요청과쓰레드는효율적이면서도효과적인입력처리를위해매우중요함.
AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.example.translate" android:versioncode="1" android:versionname="1.0"> <uses-permission android:name="android.permission.internet" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".translate" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-sdk android:minsdkversion="3" /> </manifest>
main.xml <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TableLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:stretchcolumns="1" android:padding="10dip"> <TableRow> <TextView android:text="@string/from_text" /> <Spinner android:id="@+id/from_language" /> </TableRow> <EditText android:id="@+id/original_text" android:hint="@string/original_hint" android:padding="10dip" android:textsize="18sp" /> <TableRow> <TextView android:text="@string/to_text" /> <Spinner android:id="@+id/to_language" /> </TableRow> <TextView android:id="@+id/translated_text" android:padding="10dip" android:textsize="18sp" /> <TextView android:text="@string/back_text" /> <TextView android:id="@+id/retranslated_text" android:padding="10dip" android:textsize="18sp" /> </TableLayout> </ScrollView>
arrays.xml <resources> <array name="languages"> <item>bulgarian (bg)</item> <item>chinese Simplified (zh-cn)</item> <item>chinese Traditional (zh-tw)</item> <item>catalan (ca)</item> <item>croatian (hr)</item> <item>czech (cs)</item> <item>danish (da)</item> <item>dutch (nl)</item> <item>english (en)</item> <item>filipino (tl)</item> <item>finnish (fi)</item> <item>french (fr)</item> <item>german (de)</item> <item>greek (el)</item> <item>indonesian (id)</item> <item>italian (it)</item> <item>japanese (ja)</item> <item>korean (ko)</item> <item>latvian (lv)</item> <item>lithuanian (lt)</item> <item>norwegian (no)</item> <item>polish (pl)</item> <item>portuguese (pt-pt)</item> <item>romanian (ro)</item> <item>russian (ru)</item> <item>spanish (es)</item> <item>serbian (sr)</item> <item>slovak (sk)</item> <item>slovenian (sl)</item> <item>swedish (sv)</item> <item>ukrainian (uk)</item> </array> </resources>
Translate.java (1/4) public class Translate extends Activity { private Spinner fromspinner; private Spinner tospinner; private EditText origtext; private TextView transtext; private TextView retranstext; private TextWatcher textwatcher; private OnItemSelectedListener itemlistener; private Handler guithread; private ExecutorService transthread; private Runnable updatetask; private Future transpending; @Override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); initthreading(); findviews(); setadapters(); setlisteners(); @Override protected void ondestroy() { // Terminate extra threads here transthread.shutdownnow(); super.ondestroy();
Translate.java (2/4) /** Get a handle to all user interface elements */ private void findviews() { fromspinner = (Spinner) findviewbyid(r.id.from_language); tospinner = (Spinner) findviewbyid(r.id.to_language); origtext = (EditText) findviewbyid(r.id.original_text); transtext = (TextView) findviewbyid(r.id.translated_text); retranstext = (TextView) findviewbyid(r.id.retranslated_text); /** Define data source for the spinners */ private void setadapters() { // Spinner list comes from a resource, // Spinner user interface uses standard layouts ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.languages,android.R.layout.simple_spinner_item); adapter.setdropdownviewresource(android.r.layout.simple_spinner_dropdown_item); fromspinner.setadapter(adapter); tospinner.setadapter(adapter); // Automatically select two spinner items fromspinner.setselection(8); // English (en) tospinner.setselection(11); // French (fr) /** Setup user interface event handlers */ private void setlisteners() { // Define event listeners textwatcher = new TextWatcher() { public void beforetextchanged(charsequence s, int start,int count, int after) { /* Do nothing */ public void ontextchanged(charsequence s, int start,int before, int count) { queueupdate(1000 /* milliseconds */); public void aftertextchanged(editable s) { /* Do nothing */ ; itemlistener = new OnItemSelectedListener() { public void onitemselected(adapterview parent, View v,int position, long id) { queueupdate(200 /* milliseconds */); public void onnothingselected(adapterview parent) { /* Do nothing */ ; // Set listeners on graphical user interface widgets origtext.addtextchangedlistener(textwatcher); fromspinner.setonitemselectedlistener(itemlistener); tospinner.setonitemselectedlistener(itemlistener);
Translate.java (3/4) /** Initialize multi-threading. There are two threads: 1) The main graphical user interface thread already started by Android, and 2) The translate thread, which we start using an executor. */ private void initthreading() { guithread = new Handler(); transthread = Executors.newSingleThreadExecutor(); // This task does a translation and updates the screen updatetask = new Runnable() { public void run() { // Get text to translate String original = origtext.gettext().tostring().trim(); // Cancel previous translation if there was one if (transpending!= null) transpending.cancel(true); // Take care of the easy case if (original.length() == 0) { transtext.settext(r.string.empty); retranstext.settext(r.string.empty); else { // Let user know we're doing something transtext.settext(r.string.translating); retranstext.settext(r.string.translating); // Begin translation now but don't wait for it try { TranslateTask translatetask = new TranslateTask( Translate.this, // reference to activity original, // original text getlang(fromspinner), // from language getlang(tospinner) // to language ); transpending = transthread.submit(translatetask); catch (RejectedExecutionException e) { // Unable to start new task transtext.settext(r.string.translation_error); retranstext.settext(r.string.translation_error); ;
Translate.java (4/4) /** Extract the language code from the current spinner item */ private String getlang(spinner spinner) { String result = spinner.getselecteditem().tostring(); int lparen = result.indexof('('); int rparen = result.indexof(')'); result = result.substring(lparen + 1, rparen); return result; /** Request an update to start after a short delay */ private void queueupdate(long delaymillis) { // Cancel previous update if it hasn't started yet guithread.removecallbacks(updatetask); // Start an update if nothing happens after a few milliseconds guithread.postdelayed(updatetask, delaymillis); /** Modify text on the screen (called from another thread) */ public void settranslated(string text) { guisettext(transtext, text); /** Modify text on the screen (called from another thread) */ public void setretranslated(string text) { guisettext(retranstext, text); /** All changes to the GUI must be done in the GUI thread */ private void guisettext(final TextView view, final String text) { guithread.post(new Runnable() { public void run() { view.settext(text); );
strings.xml <resources> <string name="app_name">translate</string> <string name="from_text">from:</string> <string name="to_text">to:</string> <string name="back_text">and back again:</string> <string name="original_hint">enter text to translate</string> <string name="empty"></string> <string name="translating">translating...</string> <string name="translation_error">(translation error)</string> <string name="translation_interrupted">(translation interrupted)</string> </resources>
요약 인텐트로다른액티비티나프로그램을실행시켜보았다. 웹뷰를통해웹화면을액티비티화면의일부로구성하였다. 자바스크립트를통해안드로이드프로그램의메서드들을호출하였다. 안드로이드응용프로그램에서웹서비스를이용하였다.
퀴즈 인텐트로브라우징하는것과웹뷰와는어떤차이가있는가? 웹뷰로브라우징하는경우, AndroidManifest 에서퍼미션을지정해야하는이유는무엇인가? 여기서소개된 Toast 는어떤일을하는가? 자바스크립트와자바간의호출프로그램에서소개된크롬이란무엇을의미하는가? 자바스크립트와자바간의호출프로그램에서 index.html 은왜 asset 디렉토리에들어가는가? 번역프로그램에서 ExecutorService 클래스가하는일은무엇이고, Future 클래스가하는일은무엇인가? JSON 은무엇인가? 안드로이드에서어떻게프로그래밍하는가? AJAX 는무엇인가?
연습문제 하나의액티비티화면에두개의웹뷰를띄워서브라우징하는프로그램을구현하라. 본강의에서구현된브라우저들은 http://www.dongseo.ac.kr/ 과같이 URL 를제대로써줘야동작한다. www.dongseo.ac.kr 처럼불완전한 URL 에도작동하도록프로그램을수정하라. 계산기처럼두숫자의더하기, 빼기, 곱하기, 나누기값을반환하는웹서비스를만들고, 이를안드로이드응용프로그램에서호출하여사용자가입력한두수의연산결과를보이는프로그램을작성하라. JSON 형식을사용하여, 간단한 1 대 1 채팅프로그램을구현하라. 뉴스와구글지도 ( 또는다음지도, 또는네이버지도 ) 를결합한매시업서비스를고려해보라. 이서비스는뉴스문서를해석하여관련된지역의지도를보여준다.