読者です 読者をやめる 読者になる 読者になる

とりあえず移転してみました

ニュースねたや、IT系の記事を書いていくつもり・・・ですが、どうなるかわかりません。まあ、とりあえず やってみます。

android 非同期処理まとめ

非同期処理は必要か?

androidで時間のかかる処理を行う場合には、マルチスレッドを利用してバックグランドで処理をする必要があります。新しいAPIでは、メインのスレッドでURLアクセスやDBアクセスを記述するだけでエラーになります。厳しい!!!

なので、必ず非同期処理が必要になってきます。
まずは、URLアクセス

okhttp3

便利なライブラリがあるので使います。okhttp3です。
build.gradle(app)にライブラリを使う宣言をします。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.squareup.okhttp3:okhttp:3.6.0' //<==追加

使用するclassでは、いつものようにimport文が必要。

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

これからが、本番です。

protected void urlAccess(String str){
        OkHttpClient client = new OkHttpClient();
        //RequestBody requestbodey = new FormBody.Builder()
        //        .build();

        Request request = new Request.Builder()
                .url(str)
                //.post(requestbodey)
                .get()
                .build();

        client.newCall(request).enqueue(new Callback() {
            final Handler mainHandler = new Handler(Looper.getMainLooper());
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String result = response.body().string();
                String callString = call.toString();
		//ここにViewの操作を記述する
                }
	}
}

メソッドを作成して、URLアクセスをする場合は、メソッドを呼び出すようにしています。URLは、引数として渡します。
POSTの場合の処理は、変わるのでコメントアウトしている部分がPOSTの場合の処理です。
サーバーからレスポンスがあった場合には、onResponseメソッドが実行されます。しかし、Handlerに登録するだけで実行されるのはHandlerの処理が空いた時になります。

この記述だけで非同期でのURLアクセスが可能になります。
ただ、このままでは足りません。非同期処理をメインスレッドで行わないのは、時間のかかる処理を別で行う間に画面上では別の処理を行いプログラムが止まったのか?重い処理をしているのか?を知らせる必要があります。

そのための非同期処理なので、追加でプログレスダイヤログを表示させます。

//フィールドに追加
private ProgressDialog progressDialog;

//メソッドとして追加
private void showProgressDialog(){
        progressDialog = new ProgressDialog(this);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        progressDialog.setMessage("WEBアクセス中です");
        progressDialog.setCancelable(true);
        progressDialog.show();
    }

showProgressDialog()を呼び出せば「WEBアクセス中です」と表示されたプログレスダイヤログが表示されます。
これで、プログラムがなにをやっているかわかります。

URLアクセスがあったら、次のコードで表示を消します。

progressDialog.dismiss();

Viewの操作になりますので、onResponseメソッド内に追加します。

@Override
            public void onResponse(Call call, Response response) throws IOException {
                String result = response.body().string();
                String callString = call.toString();
		//ここにViewの操作を記述する
        progressDialog.dismiss();
                }

okhttp3ライブラリのおかげで、非同期処理も比較的楽に実装できます。

次に、AsyncTaskLoaderを使う方法です。ネット上でも色々とコードが出ていますので、参考に。

AsyncTaskLoader

まずは、AsyncTaskLoaderを継承した独自クラスを作成します。だいたい、こんなパターンで始まります。面倒なのですが。

public class AsyncDbLoader extends AsyncTaskLoader<List> implements LoaderManager.LoaderCallbacks{
    Bundle bundle;

    public AsyncDbLoader(Context context,Bundle args) {
        super(context);
        bundle = args;
    }

    @Override
    public List loadInBackground() {
        List listData;
        SQLiteDatabase myDatabase;
        Sql_db helper = new Sql_db(getContext());
        myDatabase = helper.getReadableDatabase();

        Cursor c = null;
        try {
            c = myDatabase.query("mainTable", null, null, null, null, null, null);

            listData = new ArrayList();

            //adapterにDBから文字列を追加
            boolean isEof = c.moveToFirst();
            while (isEof) {
                ArrayList<String> rowstring = new ArrayList<String>();
                rowstring.add(c.getString(1));
                rowstring.add(c.getString(2));

                listData.add(rowstring);
                isEof = c.moveToNext();
            }
        }
            finally {
            if (c != null) {
                c.close();
            }
            myDatabase.close();
            helper.close();
        }
        return listData;
    }

    @Override
    public void onStartLoading(){
        forceLoad();
    }

    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        return null;
    }

    @Override
    public void onLoadFinished(Loader loader, Object data) {

    }

    @Override
    public void onLoaderReset(Loader loader) {

    }
}

実際にSQLiteからデータを取得しているコードですが、ここで重要なのはloadInBackgroundメソッドで結局、ここに実行する中身を記述するだけです。実行した結果を戻り値として返しています。

非同期でなければ、DBから取得した値をTextViewなどに書き込みわけですが、一旦List形式などにして返す必要があります。それを、メインスレッド側が受け取って、Viewに反映するという事になります。

呼び出す側のコードは、こんな感じで使います。showProgressDialogメソッドも入っていますので、プログレスダイヤログも表示されます。

getLoaderManager().initLoader(0,args, new AsyncDbLoader(this,args) {
            @Override
            public Loader<List> onCreateLoader(int id, Bundle args) {
                showProgressDialog();
                return new AsyncDbLoader(getContext(),args);
            }

            @Override
            public void onLoadFinished(Loader loader, Object data) {
                progressDialog.dismiss();
                List listData = (List)data;
                CustomAdapter adapter = new CustomAdapter(getContext(),2,listData,"list");
                ListView listView = (ListView)findViewById(R.id.listView);
                listView.setAdapter(adapter);
            }
        });

onCreateLoaderメソッドで、AsyncDbLoaderクラスのloadInBackgroundが実行されます。処理が終わるとonLoadFinishedが呼び出されます。ここでは、引数として渡されたdataをListとして格納して、ListViewに表示するまでの処理を行っています。
Viewへの変更ができるのはメイン側なので、ここでしか変更できません。

これで、非同期の処理としては2つの方法が可能で、URLアクセスの場合はokhttp3で簡単に記述でき、別の処理の場合は独自クラスを作成して、そのクラス内で処理を記述。
呼び出す側は、結果を受け取ってViewに反映という処理が必要です。

まあ、面倒といえば面倒なのですが、一度中身を理解してしまえば難しく感じません。

 

android ListViewでのはまりポイント

カスタムアダプターでListView

ListViewを使って、リスト形式の表示を行う場合が多いのだけど、色々とはまってしまった。
ListViewは、単純なテキストをリスト形式するのは簡単なのだけど、ちょっとカスタムしようとする場合は、カスタムしたクラスやレイアウトが必要になってくる。

ネットでも沢山情報があるので、基本的なところはできるのだが・・・

ArrayAdapterを継承したカスタムクラスを作成

CustomAdapter
public class CustomAdapter extends ArrayAdapter {
    private LayoutInflater layoutInflater_;

    public CustomAdapter(Context context, int textViewResourceId, List<String> objects) {
        super(context, textViewResourceId, objects);
        layoutInflater_ = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {


        if (convertView == null) {
                convertView = layoutInflater_.inflate(R.layout.rowdata_list, null);
        }

        ArrayList<String> item = (ArrayList<String>)getItem(position);

                TextView textView1;
                TextView textView2;
                textView1 = (TextView) convertView.findViewById(R.id.XXX);
                textView1.setText(item.get(1).toString());
                textView2 = (TextView) convertView.findViewById(R.id.XXX);
                textView2.setText(item.get(2).toString());

        return convertView;
    }
}

R.layout.rowdata_listというレイアウトファイルも作成

レイアウトファイル
<?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">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Medium Text"
            android:id="@+id/textView1"
            android:layout_weight="3"
            android:textColor="@android:color/black"
            android:textSize="10sp" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Medium Text"
            android:id="@+id/textView2"
            android:layout_weight="1"
            android:textColor="@android:color/black"
            android:textSize="10sp" />
    </LinearLayout>
</LinearLayout>

単純にTextViewが2つ並んでいるレイアウト・・・この部分を変更することで、レイアウトの変更も可能。

使う時は、CustomAdapterを使う。表示するListViewは(R.id.listView1)として指定している。

使う時のコード例
CustomAdapter adapter = new CustomAdapter(getContext(),0,listData);//listDataは表示させるデータ
                ListView listView = (ListView)findViewById(R.id.listView1);
                listView.setAdapter(adapter);

カスタムアダプターを使う場合は、色々と用意するものがあるけど、一度作れば使いまわせる。

はまったのは、カスタムアダプターを使わない場合。
カスタムアダプターは、ArrayAdapterを継承している。ArrayAdapterだけでも、単純なものでは使える。

ArrayAdapterでの使用例(失敗パターン)
ArrayAdapter adapter = new ArrayAdapter(getContext(),0,listData);
      ListView listView = (ListView)findViewById(R.id.listView1);
            listView.setAdapter(adapter);

CustomAdapterを使わずにArrayAdapterで指定してみた。
ここではまってしまった。テストをしてみるとアプリが落ちてしまう・・・しばらく原因がわからなかった。
ArrayAdapterを調べてみると、2つの引数が間違っていた・・・ここは、レイアウトファイルを指定しなければいけない。
カスタムアダプターと違うのだ。

R.layout.rowdataというファイルを新たに作成

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:background="@android:color/holo_blue_bright"
    android:padding="3dp">
</TextView>

TextViewが1つしかない単純なレイアウト・・・

ArrayAdapterでの使用例(成功パターン)
ArrayAdapter adapter = new ArrayAdapter(getContext(),R.layout.rowdata,listData);
      ListView listView = (ListView)findViewById(R.id.listView1);
            listView.setAdapter(adapter);

通常の場合でも、レイアウトファイルはいるのか・・・(面倒だ)

次に、カスタムアダプターに戻って、2行や3行のレイアウトはできるだろうか?リストによっては、1つの行の中で色々と表示したい場合がある。複数行にまたがる場合も当然あるだろうし、表示するものが多い場合もある。ウェブで見れるのはアイコンがあったり、2行くらいしなかない。

そこで、複数行のがいけるのか?やってみた。

結果としては、出来た。ファイルが長くなるので貼り付けはしないけど、LinerLayoutの下にLinerLayoutを4つ配置しても表示された。
そのレイアウトファイルをカスタムアダプターの中で読み出してあげればいい。
R.layout.rowdata_listの部分を変更する。もしくは、R.layout.rowdata_list自体を変更してもいい。

 

android SQLiteでの重複データの扱い

androidでは、データベースとしてSQLiteが使うことができる。ライブラリを使えば色々なDBも使えるのだけど、それほどデータがない場合などでは、SQLiteで十分でしょう。

今回はまったのは、重複データの扱い。WEBアクセスをした結果を必要な部分だけ抽出してDBに保管するのだけど、毎回データが更新されるわけではないので、同じデータの場合もある。
それをDBには記録させたくない・・・

最初に考えたのは、DB上に同じデータがあるか?比較して、なければ書き込み。あれば何もしないというフロー。

考えただけで大変そうな気がする。そこで、色々と調べてみてもDBによってはMARGEというコマンドとかあるようだけど、SQLiteにはない。
困った・・・

DBにアクセスするのは、レコードを1件ずつ取得するので、レコードの読み込み => データと比較 => 書き込みorそのまま とい処理をレコードの数だけ実行することになる。

DBのテーブル設定で、uniqueを使えば同じ内容の書き込みが制限できるというで、これを活用することにした。実際には、2つの項目が同じ場合は書き込みをしないという設定。

例えば・・・アクセスした人のIDと時間。

create table Table (_id integer primary key autoincrement,name TEXT,time TEXT,unique(name,time))

こんな感じで、テーブルを作成。

nameとtimeが同じ場合は、データへの書き込みが行われなかった。

 

android 今更ながらレイアウトでのweight指定での注意点

レイアウトの作成時に、縦にテキストやらボタンを配置する場合が多い。その場合に、同じようなものであればドンドン配置すればいいのだけど、大きさを変えないとしっくりこない。

LinerLayout(Vertical)を配置して、その中に部品を配置する方法が一番簡単。LinerLayoutの高さを調整すれば、その中の部品の高さも変更できるから。

f:id:imsdJava:20170519111950p:plain
これを使って配置していく

基本の指定方法として、Layout:heightで「match_parent」(最大)か「wrap_content」(最小)の指定が可能だけど、細かい指定ができない。その場合は、Layout:weightで重みつけをすることになる。

ただ・・・ここで注意しなければいけないのは、他のブログとかでも沢山書かれているのだけど、weightで重みをつける場合は「Layout:height」は「0dp」と指定しなければいけない。

f:id:imsdJava:20170519112848p:plain
0dpと指定したうえで重みつけを指定

上の階層のレイアウトに、「weightSum」という項目があるので、weightの合計値を指定しておくと便利。

合計値を10としておいて、

パーツA 1
パーツB 3
パーツC 2
パーツD 4

という感じで指定すると全体のイメージがつかみやすい。
weightの値と、height(高さ)もしくはwidth(幅)を0dpにする。これが必須。

 

android permissionの位置が間違っていた

毎回のように繰り返していますが、今回はリリース用のファイルを作成する段階でエラーが発生。
実機でのテストも問題なかったのに・・・

調べてみるとuses-permissionの位置がよくないっぽい。そこで、manifestファイルを確認してみる。
今回のアプリでは、インターネットアクセスとWi-Fiでのアクセスも使うので、以下の2行が必要になる。

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

記述的には問題ないけど、位置か・・・

タグ内で書いていたのが問題らしい。今まであんまり意識してなくて、うまく行っていたのだろう。
修正したのが以下の内容

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.パッケージ名">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" tools:node="remove" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar" >

もう1つ問題があって、記述を追加している。アプリを登録する段階で、プライバシーポリシーを表示しないといけないと言われた。
しかし、ユーザー情報に関連するようなパーミッションは設定していないのだが・・・

そこで、新たに追加した

<uses-permission android:name="android.permission.GET_ACCOUNTS" tools:node="remove" />

パーミッションの中で、アカウントにアクセスする部分を削除する記述らしい。これを追加する場合は、以下の記述も追加する。

xmlns:tools="http://schemas.android.com/tools"

プライバシーポリシーの表記を入れればいいのだけど、HPでの表記とアプリ内での表記が必要になるらしい。ユーザー情報にアクセスするこはないんだけどね・・・

 

プログラムの勉強も紙でもいけるよ

こんなブログがあったので読んでみた。

www.tawashix.com

今では、家にPCがあるのは当然で、スマホのほうが身近にあるのでPCを立ち上げなくなったなんて話も聞く。
メールのチェックも、PCをたちあげなくてもスマホでできるし検索もできるので問題はない。

ただ、プログラムを書くとなるとスマホでは限界があるだろう・・・

プログラムを勉強する方法として「ドリル」という方法について書かれていたけど、自分達の世代では当然だった。そう、PCが1人1台ないんだもん。
授業でプログラムの勉強をするにしても、ホストコンピュータが立ち上がるまで待って・・・なんて時代。個人個人でPCがあるのはWindows95が普及しだしてから。

では、それまではどうしていたのか?やっぱり、紙なんです。

もう、35年以上前の話になるとソフトは磁気テープで販売されていたし、プログラムが書かれた本が多く売られていた。それを購入して、自分でその通りタイプするのだ・・・なんてアナログ。

当然、ミスタイプもあるしタイプ抜けなんてのもある。一回でプログラムが動くことなんてない。4,000行くらいのプログラムを打ち込んで動かない・・・

そこから、バグを探すのだけど今時のIDEのようにミスを指摘なんてしてくれない。自分で1行1行確認する。
なんとかバグを修正して、動かしてみると・・・くそゲームだった・・・なんてこともしばしば。

会社に入ってからも、プログラムのやっていた時は、紙にコードを印刷して確認していた。ホストコンピュータに接続されている端末は数が少ないので、ずっと使うことができないので、印刷して確認していた。

面倒なようだけど、これしかなかった。

コードを書くのは最終的な部分で、フローチャートが頭の中で作れないと結局コードは書けない。

プログラムの最初の勉強だって、本を読んで配列とか変数とか分岐とかを勉強する。別にPCなくても勉強はできる。

 

怒りすら覚えてしまう内容だ・・・

昔、少しだけ住んだことのある都市が、移住したい街ランキングなどに入っている場合あるのだけど・・・
田舎暮らしが人気があるようなのだけど、その中でも雑誌などでランキングしていたりするのだけど、住んだ経験があるので凄く違和感を感じてしまう。

少し調べてみても、「う~ん・・・ちょっとね・・・と思ってしまう」というか、怒りすら覚える内容もある。

まず、交通アクセス。
最寄の駅までの時間が書いてある。比較的大きな都市から「特急で」数時間。まあ、これくらいならね・・・しかし、この先が書いていない。特急で数時間乗れば、その街に着くのか?

着くと言えば着く。そう、その街の区域という意味では・・・市の境という意味で。
しかし、街の中心部に行くには、鉄道がないのだ・・・では、バスは???

1時間に1本あるのだろうか?というくらいの感じだ。歩いていける距離ではない・・・(歩けないこともないけど)自転車でも30分くらいかかる。信号の少ない田舎道でだ。

もっと言うと、最寄?の駅に着く特急電車も1時間に1本しかない・・・

田舎暮らしを目的にしている人だから気にしないのでは?と思うのだけど、都会に住んでいて田舎暮らしに憧れてという人だと、この現実を受け止めるのは難しいだろう。

駅からタクシーに乗っても、なかなかの距離だ。問題なのは、更に先にある。中心部に着いても、何もない。まあ、田舎だからね。
でも、移動手段がない。バスなんて、当然ない。地元の人は、当然車がある。

ところが、移住目的で下見に来る人には車なんてないだろうから、途方にくれる。

真実をもっと記載しておけばいいのに・・・電車で来ても移動手段に困るからとか、レンタカーをお勧めするとかね。

教育に力を入れているということも書かれていて、新しい図書館が出来たと綺麗な写真と中の画像があるのだけど、確かに綺麗でいい感じなのだが・・・市内の他の図書館がない・・・新しくした時に、市内の図書館を全部閉鎖したのだろう。

昔利用していた図書館は、公民館に変わっている。

市内に図書館は、1箇所・・・地方自治体の財政が厳しいのはいつものことで、大きな図書館で沢山の蔵書を管理したほうが、サービス的にもいいのは理解できる。ただ・・・この場所は移動手段がないのだ。

都内のように、バスや地下鉄などが充実していない。一番困るのは、年配の方と若者なのだ・・・一番、図書館を必要とする人達が。

近くに大きな本屋もなく、欲しい本も簡単には手に入らなかった。仕入れる本の数も少ないので、発売日から遅れると本がないというのもしばしば。

大きな本屋に行こうとしても、駅は遠い。電車も1時間に1本・・・教育という意味では、経験したものとしては薦めることはできない環境。

本当に住みやすい街だと思っているのだろうか????