メインスレッド以外でUIを変更する方法
Androidアプリにおいて、メイン(UI)スレッド以外のスレッドで、UIを変更しようとすると、実行時に下記の例外が発生する。
これは「UI部品の操作はUIスレッドから行わなければならない」というAndroidの制約のためである。
今回は、メイン(UI)スレッド以外から、UI操作を行う方法を紹介する。
<発生する例外>
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6566) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:911) at android.view.View.requestLayout(View.java:18814) at android.view.View.requestLayout(View.java:18814) at android.view.View.requestLayout(View.java:18814) ...
<例外が発生するソースコード>
MainActivity.java
package biz.accele.samplethreadui; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { final Button btn1; final TextView txt1; super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn1 = (Button)findViewById(R.id.btn1); txt1 = (TextView)findViewById(R.id.txt1); btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 別スレッドを実行 new Thread(new Runnable() { @Override public void run() { // 別スレッドでUIを変更しようとしている txt1.setText("本日は晴天なり"); } }).start(); } }); } }
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical"> <Button android:id="@+id/btn1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Go"/> <TextView android:id="@+id/txt1" android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
<例外を回避して、別スレッドからUIを変更する方法>
具体的な方法としては、Handlerを使用して、別スレッドからメイン(UI)スレッドに処理を依頼することになる。
MainActivity.java
package biz.accele.samplethreadui; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { final Button btn1; final TextView txt1; super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn1 = (Button)findViewById(R.id.btn1); txt1 = (TextView)findViewById(R.id.txt1); // メイン(UI)スレッドでHandlerのインスタンスを生成する final Handler handler = new Handler(); btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 別スレッドを実行 new Thread(new Runnable() { @Override public void run() { // Handlerを使用してメイン(UI)スレッドに処理を依頼する handler.post(new Runnable() { @Override public void run() { txt1.setText("本日は晴天なり"); } }); } }).start(); } }); } }
activity_main.xml
変更なし
今回の例の様にUI上のテキストを変更するために別スレッドにすることは無いと思うが、時間のかかる処理を別スレッドで実行し、その結果をUI上に反映したいことは、よくあるケースである。
その際は今回紹介した方法を試して頂きたい。
Enjoy Programing!!
WebView上でのリクエストをフックする方法
Androidアプリ上でWebページを表示したい場合は「WebView」を使用することになる。
今回はWebViewで表示したWebページ上のリンクがユーザによりタップされた場合に、それをアプリで検知する方法を紹介する。
<方法概要>
1.WebViewでWebページを表示できる様にする
2.WebViewにWebViewClientを設定する
<方法詳細>
1.WebViewでWebページを表示できる様にする
1.1.パーミッションの設定
「<uses-permission android:name="android.permission.INTERNET" />」をAndroidManifest.xmlに追加する。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="biz.accele.samplewebview" > <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" 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>
1.2.画面レイアウトを定義するxmlファイルにWebViewを追加する
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <WebView android:id="@+id/webView1" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
1.3.表示するURLを指定する
MainActivity.java
package biz.accele.samplewebview; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.webkit.WebView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // WebViewの取得 WebView webView1 = (WebView) findViewById(R.id.webView1); // 表示するURLを指定 webView1.loadUrl("https://www.google.co.jp/"); } }
ここまででアプリを実行すれば、Googleのトップページがアプリ内に表示される。
2.WebViewにWebViewClientを設定する
今回の本題である、Webページ上のリンクがタップされたことをアプリで検知するために、WebViewにWebViewClientを設定する。
WebViewにWebViewClientを設定するためには、WebViewクラスの「setWebViewClient」メソッドを使用する。
また、Webページ上のリンクがタップされたことをアプリで検知するためには、WebViewClientクラスの「shouldOverrideUrlLoading」メソッドをオーバーライドし、そこに検知後の処理を記述することで実施できる。
今回は単純にするために、タップされたリンクのURLを表示する例を準備した。
MainActivity.java
package biz.accele.samplewebview; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.webkit.WebView; import android.webkit.WebViewClient; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // WebViewの取得 WebView webView1 = (WebView) findViewById(R.id.webView1); // WebViewClientの設定 webView1.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.d("--- ログ --->", "タップされたリンクのurl:" + url); return true; } }); // 表示するURLを指定 webView1.loadUrl("https://www.google.co.jp/"); } }
アプリを実行して、Webページ内のリンクをタップすれば、logcatにリンクのURLが出力される。
08-25 20:12:36.401 5902-5902/biz.accele.samplewebview D/--- ログ --->﹕ タップされたリンクのurl:https://www.google.co.jp/webhp?output=search&tbm=isch&tbo=u
ただ、リンクのURLはログに表示されるが、画面がそのURLに遷移することはない。
これは、「WebViewClient#shouldOverrideUrlLoading」の戻り値として「true」を設定しているためである。
リンク先に遷移したい場合は、上記の戻り値を「false」に設定することで実施できる。
Enjoy Programing!!
ブログを新設しました
ブログを新設しました。
以下の記事は「MoonMt.Lab IT開発技術 調査報告書」から移行したものです。
Android(Java)でメソッドの呼び出し元を確認する方法
筆者は他人が作成したコードを分析する時によく使います。
呼び出し元の「クラス名」「メソッド名」「行数」が標準出力に表示されます。
StackTraceElement ste = Thread.currentThread().getStackTrace()[3]; System.out.println("caller class:" + ste.getClassName() + " metho:" + ste.getMethodName() + " line:" + ste.getLineNumber());
iOSアプリ開発で日時を扱う時の注意点(暦と24時間表示の設定)
今回は、iOSアプリ開発で日時を扱う時の注意点を紹介する。
日付型である「NSDate」型を、文字列型である「NSString」に変換する際、「NSDateFormatter」クラスを使用することが一般的である。
ただ、NSDateFormatterの設定によっては意図しない変換が行われることがあるので注意が必要である。
現在日時を「yyyy/MM/dd HH:mm:ss」形式の文字列に変換する場合、最小限の実装は以下の様になる。
//現在時刻を取得する NSDate *now = [NSDate date]; //フォーマッターをインスタンス化する NSDateFormatter *df = [[NSDateFormatter alloc] init]; //変換フォーマットの設定 df.dateFormat = @"yyyy/MM/dd HH:mm:ss"; //NSDate型からNSString型にフォーマットに従って変換する NSString *strDate = [df stringFromDate:now]; //結果をログに出力する NSLog(@"文字列時刻:%@", strDate);
このロジックを動作させた場合、iOS端末の設定によって、結果に以下の様なバラつきが起こる。
No. | 暦 | 24時間表示 | 結果 |
---|---|---|---|
1 | 西暦 | ON | 2015/03/21 11:08:31 |
2 | 和暦 | ON | 0027/03/21 11:09:03 |
3 | 西暦 | OFF | 2015/03/21 午前11:10:24 |
「暦」の設定は[設定]>[一般]>[言語と地域]>[カレンダ]で行い、「24時間表示の設定」は[設定]>[一般]>[日付と時刻]>[24時間表示]で行う。
実装者が望む結果は「No.2とNo.3の結果もNo.1と同じであること」と考えている。
よって、No.2とNo.3の結果がNo.1と同じになる、NSDateFormatterの設定を紹介する。
下記の様にNSDateFormatterの設定に3行追加する。
//現在時刻を取得する NSDate *now = [NSDate date]; //フォーマッターをインスタンス化する NSDateFormatter *df = [[NSDateFormatter alloc] init]; df.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; //...(1) df.locale = [NSLocale systemLocale]; //...(2) df.timeZone = [NSTimeZone systemTimeZone]; //...(2) //変換フォーマットの設定 df.dateFormat = @"yyyy/MM/dd HH:mm:ss"; //NSDate型からNSString型にフォーマットに従って変換する NSString *strDate = [df stringFromDate:now]; //結果をログに出力する NSLog(@"文字列時刻:%@", strDate);
<ソースコード解説>
番号 | 説明 |
---|---|
(1) | NSDateFormatterのcalendar属性に「NSGregorianCalendar」を設定することで、端末の暦の設定による結果のバラつきをなくす。 |
(2) | NSDateFormatterのlocale属性・timeZone属性を明示的に設定することで、端末の24時間表示の設定による結果のバラつきをなくす。 |
<実行結果>
No. | 暦 | 24時間表示 | 結果 |
---|---|---|---|
1 | 西暦 | ON | 2015/03/21 11:54:03 |
2 | 和暦 | ON | 2015/03/21 11:54:34 |
3 | 西暦 | OFF | 2015/03/21 11:54:53 |
Enjoy Programing!!
<お勧め書籍>
REST風サービスをJavaEEで構築する方法13(SQLログ編)
今回はEntityManagerで実行したSQLをログに出力する方法を紹介する。
REST風サービスをJavaEEで構築する方法09(DB接続設定編)で生成した「persistence.xml」を編集する。
プロジェクトツリー上の[構成ファイル]>[persistence.xml]をダブルクリックしてファイルを開き、上部の【ソース】ボタンを押下する。
表示されたxmlファイルの「properties」タグに下記の2行を追加する。
<property name="eclipselink.logging.level.sql" value="FINE"/> <property name="eclipselink.logging.parameters" value="true"/>
※「properties」タグが存在しない場合は追加する
追加した結果、「persistence.xml」ファイルが下記の様な状態であることを確認する。
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="SampleRestPU" transaction-type="JTA"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <jta-data-source>jdbc/sample_db</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <validation-mode>NONE</validation-mode> <properties> <property name="eclipselink.logging.level.sql" value="FINE"/> <property name="eclipselink.logging.parameters" value="true"/> </properties> </persistence-unit> </persistence>
<動作確認>
プロジェクトを実行し、DBに対してSQLが発行される様な処理を実行する。
NetBeansの「出力」タブの「GlassFish Server 4.1」タブに、実行されたSQLとSQL中の変数にバインドされた値が出力される。
普通: select s.ID, d.DETAIL_ID, s.NAME, d.REMARKS from SAMPLE_TBL s, SAMPLE_DETAIL_TBL d where s.ID = d.ID and d.REMARKS = ? and d.DETAIL_ID = ? order by s.ID, d.DETAIL_ID; bind => [LMNOPQRSTUV, 02]
Enjoy Programing!!
<関連記事>
・REST風サービスをJavaEEで構築する方法01(導入編)
・REST風サービスをJavaEEで構築する方法02(雛形プロジェクト編)
・REST風サービスをJavaEEで構築する方法03(RESTクライアント編)
・REST風サービスをJavaEEで構築する方法04(各メソッド編)
・REST風サービスをJavaEEで構築する方法05(パラメータの受け取り編)
・REST風サービスをJavaEEで構築する方法06(JSON返却編1)
・REST風サービスをJavaEEで構築する方法07(JSON返却編2)
・REST風サービスをJavaEEで構築する方法08(Logic層編)
・REST風サービスをJavaEEで構築する方法09(DB接続設定編)
・REST風サービスをJavaEEで構築する方法10(DAO層編1)
・REST風サービスをJavaEEで構築する方法11(DAO層編2)
・REST風サービスをJavaEEで構築する方法12(DAO層編3)
・REST風サービスをJavaEEで構築する方法13(SQLログ編)[本記事]
<お勧め書籍>
REST風サービスをJavaEEで構築する方法12(DAO層編3)
前回はSQLを使用して、1つテーブルに対して検索する方法を紹介した。
今回はSQLを使用して、複数テーブルを結合した検索の方法を紹介する。
<前提条件>
下記の記事を読了していること。
・REST風サービスをJavaEEで構築する方法01(導入編)
・REST風サービスをJavaEEで構築する方法02(雛形プロジェクト編)
・REST風サービスをJavaEEで構築する方法03(RESTクライアント編)
・REST風サービスをJavaEEで構築する方法04(各メソッド編)
・REST風サービスをJavaEEで構築する方法05(パラメータの受け取り編)
・REST風サービスをJavaEEで構築する方法06(JSON返却編1)
・REST風サービスをJavaEEで構築する方法07(JSON返却編2)
・REST風サービスをJavaEEで構築する方法08(Logic層編)
・REST風サービスをJavaEEで構築する方法09(DB接続設定編)
・REST風サービスをJavaEEで構築する方法10(DAO層編1)
・REST風サービスをJavaEEで構築する方法11(DAO層編2)
接続するDB(MySQL)に下記のテーブルが作成されていること。
(前回まで使用してきた「SAMPLE_TBL」と結合するテーブルとして追加する)
テーブル名:SAMPLE_DETAIL_TBL
No. | カラム名 | 型 | 桁 | 主キー | 制約 | |
---|---|---|---|---|---|---|
1 | ID | CHAR | 4 | 〇 | NOT NULL | |
2 | DETAIL_ID | CHAR | 2 | 〇 | NOT NULL | |
3 | REMARKS | VARCHAR | 256 | - |
CompositeKeyクラスを作成する。EntityクラスとDao層クラスを追加作成する。
<手順概要>
1.CompositeKeyクラスを作成する
2.Entityクラスを作成する
3.Dao層クラスを作成する
4.Logic層・Api層・index.htmlを編集する
<手順詳細>
1.CompositeKeyクラスを作成する
Entityクラスでは主キーを表す「@Id」を付与できるプロパティは基本的に1つだけである。
複数の列の組み合わせをキーとする場合は、キーの要素となるプロパティを持つ、CompositeKeyクラスを作成し、Entityクラスの@IdClassアノテーションで指定する必要がある。
プロジェクトツリー上で右クリックし、[新規]>[Javaクラス]を選択する。
表示されたダイアログのクラス名に「SampleJoinKey」(※)、パッケージに「lab.moonmt.SampleRest.entity.key」(※)と設定し、【終了(F)】ボタンを押下する。
(※)任意の名前をつけることができる。
作成されたクラスを下記の様に編集する。
package lab.moonmt.SampleRest.entity.key; import java.io.Serializable; public class SampleJoinKey implements Serializable { public String id; //...(1) public String detail_id; //...(1) //...(※※) }ここまで記述して、(※※)付近で右クリックし「コードの挿入...」を選択する。
表示されたメニューで「equals()およびhashCode()...」を押下する。
それぞれのメソッドに含めるフィールドを聞かれるので、すべてにチェックをいれて、【生成】ボタンを押下する。
メソッドが自動生成されて、下記の様な状態になったことを確認する。
package lab.moonmt.SampleRest.entity.key; import java.io.Serializable; import java.util.Objects; public class SampleJoinKey implements Serializable { public String id; public String detail_id; @Override public int hashCode() { int hash = 5; hash = 97 * hash + Objects.hashCode(this.id); hash = 97 * hash + Objects.hashCode(this.detail_id); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final SampleJoinKey other = (SampleJoinKey) obj; if (!Objects.equals(this.id, other.id)) { return false; } if (!Objects.equals(this.detail_id, other.detail_id)) { return false; } return true; } }
2.Entityクラスを作成する
プロジェクトツリー上で右クリックし、[新規]>[Javaクラス]を選択する。
表示されたダイアログのクラス名に「SampleJoinEntity」(※)、パッケージに「lab.moonmt.SampleRest.entity」(※)と設定し、【終了(F)】ボタンを押下する。
(※)任意の名前をつけることができる。
作成されたクラスを下記の様に編集する。
package lab.moonmt.SampleRest.entity; import lab.moonmt.SampleRest.entity.key.SampleJoinKey; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; @Entity @IdClass(value=SampleJoinKey.class) //...(1) @NamedNativeQueries({ //...(2) @NamedNativeQuery( //...(3) name="joinSql", query="select s.ID, d.DETAIL_ID, s.NAME, d.REMARKS " + "from SAMPLE_TBL s, SAMPLE_DETAIL_TBL d " + "where s.ID = d.ID " + "order by s.ID, d.DETAIL_ID;", resultClass=SampleJoinEntity.class), @NamedNativeQuery( //...(4) name="joinSql2", query="select s.ID, d.DETAIL_ID, s.NAME, d.REMARKS " + "from SAMPLE_TBL s, SAMPLE_DETAIL_TBL d " + "where s.ID = d.ID " + "and d.REMARKS = ?remarks " + "and d.DETAIL_ID = ?dId " + "order by s.ID, d.DETAIL_ID;", resultClass=SampleJoinEntity.class) }) public class SampleJoinEntity implements Serializable { @Id private String id; //...(5) @Id private String detail_id; //...(5) private String name; private String remarks; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getDetail_id() { return detail_id; } public void setDetail_id(String detail_id) { this.detail_id = detail_id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRemarks() { return remarks; } public void setRemarks(String remarks) { this.remarks = remarks; } }
ソースコード解説
番号 | 説明 |
---|---|
(1) | @IdClassアノテーションを使用して、手順1で作成したCompositeKeyクラスを指定している。 |
(2) | NamedNativeQueriesアノテーションを使用して、複数のSQLを記述することを宣言している。 |
(3) | @NamedNativeQueryアノテーションを使用して、1つ目のSQLを記述している。(記述しているSQLは2つのテーブルを結合している) |
(4) | @NamedNativeQueryアノテーションを使用して、2つ目のSQLを記述している。(記述しているSQLは2つのテーブルを結合している) |
(5) | (1)でCompositeKeyクラスを@IdClassで指定しているので、複数のプロパティに@Idをつけても警告が発生しない。 |
プロジェクトツリー上で右クリックし、[新規]>[Javaクラス]を選択する。
表示されたダイアログのクラス名に「SampleJoinDao」(※)、パッケージに「lab.moonmt.SampleRest.dao」(※)と設定し、【終了(F)】ボタンを押下する。
(※)任意の名前をつけることができる。
作成されたクラスを下記の様に編集する。
package lab.moonmt.SampleRest.dao; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import lab.moonmt.SampleRest.entity.SampleJoinEntity; @Stateless public class SampleJoinDao { @PersistenceContext private EntityManager em; public List<SampleJoinEntity> getJoinInfo(){ Query query = em.createNamedQuery("joinSql"); //...(1) return query.getResultList(); //...(3) } public List<SampleJoinEntity> getJoinInfo2(String dId, String remarks){ Query query = em.createNamedQuery("joinSql2"); //...(1) query.setParameter("dId", dId); //...(2) query.setParameter("remarks", remarks); //...(2) return query.getResultList(); //...(3) } }
ソースコード解説
番号 | 説明 |
---|---|
(1) | エンティティマネージャの「createNamedQuery」メソッドを使用して、Entityクラスに記述したSQL文を読み込む。引数はEntityクラスでSQL文につけた名前を指定する。 |
(2) | SQL文中の変数に対して値を設定している。 |
(3) | SQL文を実行し、結果を呼び出し元に返却している。 |
手順3で追加したメソッドが呼び出される様に、Logic層・Api層クラスおよびindex.htmlファイルを編集する。
詳細は割愛する。
Enjoy Programing!!
<関連記事>
・REST風サービスをJavaEEで構築する方法01(導入編)
・REST風サービスをJavaEEで構築する方法02(雛形プロジェクト編)
・REST風サービスをJavaEEで構築する方法03(RESTクライアント編)
・REST風サービスをJavaEEで構築する方法04(各メソッド編)
・REST風サービスをJavaEEで構築する方法05(パラメータの受け取り編)
・REST風サービスをJavaEEで構築する方法06(JSON返却編1)
・REST風サービスをJavaEEで構築する方法07(JSON返却編2)
・REST風サービスをJavaEEで構築する方法08(Logic層編)
・REST風サービスをJavaEEで構築する方法09(DB接続設定編)
・REST風サービスをJavaEEで構築する方法10(DAO層編1)
・REST風サービスをJavaEEで構築する方法11(DAO層編2)
・REST風サービスをJavaEEで構築する方法12(DAO層編3)[本記事]
・REST風サービスをJavaEEで構築する方法13(SQLログ編)
<お勧め書籍>