2012年9月28日金曜日

Android エミュレータのSDカードにファイルを配置する

まずエミュレータにSDカードを作成します。
エクリプスのメニューより「ウィンドウ」→「AVDマネージャー」を選択します。
開いたダイアログよりSDカードを作成するエミュレータを選択し「編集」ボタンをクリックします。

SDカードに512MのSDカードを作成しました。
「AVDの編集」をクリックしダイアログを終了します。

SDカードが作成できたら「開始」ボタンでエミュレータを起動します。

エミュレータが起動したらエクリプスのメニューより「ウィンドウ」→「パースペクティブを開く」→「その他」→「DDMS」を選択します。 「ファイル・エクスプローラ」タブにsdcardが見えていると思います。

私のエミュレータではmntフォルダの下にsdcardが有りました。
最初はトップ階層のsdcardだと思って「なんでフォルダじゃないんだろう」なんて悩んでしまいました。
情報列の「mnt/sdcard」の意味がわからなかった・・・

右上の「pull a file」ボタンと[push a file」ボタンでSDカードにファイルを配置したり取り出したりできます。
私の環境では日本語名のファイルは「push a file」ボタンで配置できませんでした。
DDMSでファイルを配置できない場合は、コマンドプロンプトから以下のコマンドを入力します。

※ファイルを配置するには
>adb push C:\Sample.txt /sdcard/
adb push <PCにあるファイル> <デバイスのフォルダ>

※ファイルを取り出すには
adb pull <デバイスファイル名> <PCのファイル名>

2012年9月27日木曜日

Android TabActivityとTabHostを使用してTab画面を表示する

2012/11/19 追記
TabHostを使用する方法はAndroid 3.0 以降は非推奨となりました。
TabHostを使用せずにタブ画面を表示する方法はコチラ
Android ActionBarとFragmentを使用してTab画面を表示する(Android 4.0以上)
Android ActionBarとFragmentを使用してTab画面を表示する(Android 2.x)



むかし2年程前にタブメニューについて調べたんです。コレ→「Android タブ画面を表示する
今回タブを使おうと思って昔の記事を読んでみたんですが・・・全然わかんないわぁ(;´д`)
自分で読んでも何を書いてるのかわかんないのに、二人の人が役にたったとリアクションしてくれてます。ゴメンナサイ

結局、私の役にはたたなかったので、ほかの人のブログを参考にしました。
自分の調べた事をメモるブログなのに意味ないしっ(>_<)

気を取り直して、再度調べた事をまとめておきます。

タブメニューを作成するにはTabHostウィジェットを使用します。
ActivityにTabHostを配置し、ActivityのonCreate()でTabHostにタブを追加します。
各タブをクリックしたときに表示するコンテンツですが、以下の3通りがあるようです。
  1. TabHostを配置したアクティビティに定義したViewを表示する。
  2. 他のアクティビティを表示する
  3. layoutファイルに定義したViewを表示する

TabHostを配置したアクティビティに定義したViewを表示する

ActivityにTabHostを配置します。

アウトラインを見ると以下のようになっていると思います。
TabHost(id/tabhost)
 |--LinearLayout
    |--TabWidget(id/tabs)
      |--FrameLayout(id/tabcontent)
        |--LinearLayout(id/tabs1)
        |--LinearLayout(id/tabs2)
        |--LinearLayout(id/tabs3)

注意点は以下の3点です。
  • TabHostはidがtabhostであること。
  • TabWidgetはidがtabsであること。
  • FrameLayoutはidがtabcontentであること。

3つのLinearLayout(idがtab1,tab2,tab3)が各タブをクリックしたときに表示されるコンテンツになります。
タブを4つにしたければLinearLayoutをもう一つ追加します。

それでは各LinearLayoutに適当にウイジェットを配置します。
何か方法があるのかもしれませんが、2つめ以降のLinearLayoutをGraphicalLayoutでデザイン作成できないのが不便です。
アウトラインに追加し、XMLを編集していくしかないのですかね(´д`)

今回は各LinearLayoutにTextViewを1つずつ配置しました。

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" >

    <TabHost
        android:id="@android:id/tabhost"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" >

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

            <TabWidget
                android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >
            </TabWidget>

            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="match_parent"
                android:layout_height="match_parent" >

                <LinearLayout
                    android:id="@+id/tab1"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" >

                    <TextView
                        android:id="@+id/textView1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Tabページ1" />

                </LinearLayout>

                <LinearLayout
                    android:id="@+id/tab2"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" >

                    <TextView
                        android:id="@+id/textView2"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Tabページ2" />

                </LinearLayout>

                <LinearLayout
                    android:id="@+id/tab3"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" >

                    <TextView
                        android:id="@+id/textView3"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Tabページ3" />

                </LinearLayout>

            </FrameLayout>
        </LinearLayout>
    </TabHost>

</RelativeLayout>

タブ部分に表示するテキストをres/values/strings.xmlに定義します。
<resources>
    :
    :
    <string name="tab1">ページ1</string>
    <string name="tab2">ページ2</string>
    <string name="tab3">ページ3</string>
</resources>

ActivityはTabActivityを継承して作成し、onCreate()でTabHostに各タブを追加していきます。
TabSpec#setIndicator()でタブ部分の文字を指定します。
TabSpec#setContent()でタブをクリックした時に表示するViewのidを指定します。
ここではactivity_main.xmlにある3つのLinearLayoutのidです。

MainActivity.java
public class MainActivity extends TabActivity {
        
    private static final String TAB[] = {"tab1", "tab2","tab3" };   
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //TabHostオブジェクト取得   
        TabHost tabhost = getTabHost();
        //Tab1設定   
        TabSpec tab1 = tabhost.newTabSpec(TAB[0]);   
        tab1.setIndicator(this.getResources().getString(R.string.tab1));       
        tab1.setContent(R.id.tab1);
        tabhost.addTab(tab1);       
        //Tab2設定   
        TabSpec tab2 = tabhost.newTabSpec(TAB[1]);   
        tab2.setIndicator(this.getResources().getString(R.string.tab2));      
        tab2.setContent(R.id.tab2);
        tabhost.addTab(tab2);   
        //Tab3設定   
        TabSpec tab3 = tabhost.newTabSpec(TAB[2]);   
        tab3.setIndicator(this.getResources().getString(R.string.tab3));      
        tab3.setContent(R.id.tab3);
        tabhost.addTab(tab3);       
        //初期表示するタブ   
        tabhost.setCurrentTab(0);  
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    } 

}

他のアクティビティを表示する

次はタブをクリックした時に表示するコンテンツにActivityを指定する方法です。

まずTabHostを配置したメインとなるアクティビティを1つ作成します。
そしてタブをクリックした時に表示するアクティビティをタブの数だけ作成します。

つまりタブが3つある画面であれば4つのアクティビティを作成することになります。

各タブのコンテンツとなるアクティビティ「Tab1Activity.java」、「Tab2Activity.java」、「Tab3Activity.java」を作成します。
各アクティビティには、それぞれの違いがわかるように適当にウィジェットを配置しておきます。

次にメインとなるActivityを作成します。
こちらは「TabHostを配置したアクティビティに定義したViewを表示する」で作成したMainActivity.javaとほぼ同じです。
違いは、TabSpec#setContent()でアクティビティを指定します。

MainActivity.java
public class MainActivity extends TabActivity {
        
    private static final String TAB[] = {"tab1", "tab2","tab3" };   
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //TabHostオブジェクト取得   
        TabHost tabhost = getTabHost();
        //Tab1設定   
        TabSpec tab1 = tabhost.newTabSpec(TAB[0]);   
        tab1.setIndicator(this.getResources().getString(R.string.tab1));       
        tab1.setContent(new Intent().setClass(this, Tab1Activity.class));
        tabhost.addTab(tab1);       
        //Tab2設定   
        TabSpec tab2 = tabhost.newTabSpec(TAB[1]);   
        tab2.setIndicator(this.getResources().getString(R.string.tab2));      
        tab2.setContent(new Intent().setClass(this, Tab2Activity.class));
        tabhost.addTab(tab2);   
        //Tab3設定   
        TabSpec tab3 = tabhost.newTabSpec(TAB[2]);   
        tab3.setIndicator(this.getResources().getString(R.string.tab3));      
        tab3.setContent(new Intent().setClass(this, Tab3Activity.class));
        tabhost.addTab(tab3);       
        //初期表示するタブ
        tabhost.setCurrentTab(0);  
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    } 

}
この方法はアクティビティがいっぱいになるのがイヤだな・・・

layoutファイルに定義したViewを表示する

最後にTabをクリックした時に表示するコンテンツに、res/layoutに作成したファイルを表示する方法です。
これが一番よいのではと思っています。

res/layoutを右クリックし「新規」→「その他」→「Android XML レイアウト・ファイル」を選択し
「tab1.xml」、「tab2.xml」、「tab3.xml」を作成します。

ルート要素はLinearLayoutにし、それぞれの違いがわかるように適当にウィジェットを配置しておきます。

次にメインとなるActivityを作成します。
こちらは「TabHostを配置したアクティビティに定義したViewを表示する」で作成したMainActivity.javaとほぼ同じです。
違いは、TabSpec#setContent()でTabContentFactoryインターフェースを実装したクラスを指定します。
TabContentFactoryのcreateTabContent()メソッドで先ほど作成したレイアウトファイルを返すようにします。
MainActivity.java
public class MainActivity extends TabActivity {
        
    private static final String TAB[] = {"tab1", "tab2","tab3" };   
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //タブに表示するViewをlayoutより取得する
        LayoutInflater layout = LayoutInflater.from(MainActivity.this); 
        final View viewTab1 = layout.inflate(R.layout.tab1, null);
        final View viewTab2 = layout.inflate(R.layout.tab2, null);
        final View viewTab3 = layout.inflate(R.layout.tab3, null);
        //TabHostオブジェクト取得   
        TabHost tabhost = getTabHost();
        //Tab1設定   
        TabSpec tab1 = tabhost.newTabSpec(TAB[0]);   
        tab1.setIndicator(this.getResources().getString(R.string.tab1));       
        tab1.setContent(new TabHost.TabContentFactory (){
            public View createTabContent(String tag) {return viewTab1;}
        });
        tabhost.addTab(tab1);       
        //Tab2設定   
        TabSpec tab2 = tabhost.newTabSpec(TAB[1]);   
        tab2.setIndicator(this.getResources().getString(R.string.tab2));      
        tab2.setContent(new TabHost.TabContentFactory (){
            public View createTabContent(String tag) {return viewTab2;}
        });
        tabhost.addTab(tab2);  
        //Tab3設定   
        TabSpec tab3 = tabhost.newTabSpec(TAB[2]);   
        tab3.setIndicator(this.getResources().getString(R.string.tab3));      
        tab3.setContent(new TabHost.TabContentFactory (){
            public View createTabContent(String tag) {return viewTab3;}
        });
        tabhost.addTab(tab3);  
        //初期表示するタブ   
        tabhost.setCurrentTab(0);  
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    } 

}


今回はちゃんとまとめられたと思うっ( *`ω´)

2012年9月25日火曜日

Android AlertDialogに画像を表示する

以前のAndroid AlertDialogを表示する の「独自のレイアウトを表示する」方法を利用してAlertDialogに画像を表示します。

画像の用意


まずAlertDialogに配置する画像を用意します。
画像はファイルエクスプローラーから\res\drawableフォルダに直接配置します。
drawableフォルダがなければ作成してください。
Androidは .png ( 推奨 )、.jpg ( 容認 )、.gif ( 非推奨 ) の 3つのフォーマットのビットマップをサポートします。
Eclipseが画像を自動で認識して、プロジェクトエクスプローラのres/drawableにそれぞれの画像が表示されます。
画像が読み込まれない場合、メニュー「プロジェクト」→「クリーン」を行ってください。

リソースファイルの用意


AlertDialogのメッセージの文字色を白色にしたいので、色リソースを定義します。
Eclipseのプロジェクトエクスプローラのres/valuesを右クリックし「新規」→「その他」→「Android XML 値ファイル」を選択します。
ファイル名を「color」としルート要素に「resources」を選択します。
白色を定義します。
res/values/color.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
     <color name="white">#FFFFFF</color>
</resources>

レイアウトファイルの用意


Eclipseのプロジェクトエクスプローラのres/layoutを右クリックし「新規」→「その他」→「Android XML レイアウト・ファイル」を選択します。
ファイル名を「alert」としルート要素に「LinearLayout」を選択しました。

レイアウトファイルには
画像を表示するためのImageViewとメッセージを表示するためのTextViewを配置します。
TextViewのTextColorは先ほど作成した色リソースの白を指定します。
色リソースがEclipseに認識されない場合はプロジェクトのクリーンを行ってください。
res/layout/alert.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content" >
     <ImageView
          android:id="@+id/imageAlertIcon"
          android:layout_width="60dp"
          android:layout_height="60dp" />
     <TextView
          android:id="@+id/lblMessage"
          android:layout_width="258dp"
          android:layout_height="match_parent"
          android:text="TextView"
          android:textColor="@color/white" />

</LinearLayout>

AlertDialogを表示するクラスの用意


AlertDialogを表示するクラスAlertHelperクラスを作成します。(ここら辺はおこのみで)
レイアウトファイルの用意で作成したalertレイアウトを取得し
ImageViewに画像を、TextViewにメッセージを表示します。
public class AlertHelper {
    
    /**
     * 警告メッセージを表示します。
     * @param context
     * @param message
     * @param listenerOK
     */
    public static void showWarning(Context context,String message,DialogInterface.OnClickListener listenerOK){
        LayoutInflater inflater = LayoutInflater.from(context);
        final View view = inflater.inflate(R.layout.alert,null);
        ImageView imgAlertIcon = (ImageView)view.findViewById(R.id.imageAlertIcon);
        imgAlertIcon.setImageResource(R.drawable.warning);
        TextView lblMessage = (TextView)view.findViewById(R.id.lblMessage);
        lblMessage.setText(message);
        AlertDialog.Builder alert = new AlertDialog.Builder(context);        
        alert.setPositiveButton("OK",listenerOK);
        alert.setView(view);
        alert.show();    
    }
    
    /**
     * エラーメッセージを表示します。
     * @param context
     * @param message
     * @param listenerOK
     */
    public static void showError(Context context,String message,DialogInterface.OnClickListener listenerOK){
        LayoutInflater inflater = LayoutInflater.from(context);
        final View view = inflater.inflate(R.layout.alert,null);
        ImageView imgAlertIcon = (ImageView)view.findViewById(R.id.imageAlertIcon);
        imgAlertIcon.setImageResource(R.drawable.error);
        TextView lblMessage = (TextView)view.findViewById(R.id.lblMessage);
        lblMessage.setText(message);
        AlertDialog.Builder alert = new AlertDialog.Builder(context);        
        alert.setPositiveButton("OK",listenerOK);
        alert.setView(view);
        alert.show();    
    }
    
    /**
     * 情報メッセージを表示します。
     * @param context
     * @param message
     * @param listenerOK
     */
    public static void showInformation(Context context,String message,DialogInterface.OnClickListener listenerOK){
        LayoutInflater inflater = LayoutInflater.from(context);
        final View view = inflater.inflate(R.layout.alert,null);
        ImageView imgAlertIcon = (ImageView)view.findViewById(R.id.imageAlertIcon);
        imgAlertIcon.setImageResource(R.drawable.information);
        TextView lblMessage = (TextView)view.findViewById(R.id.lblMessage);
        lblMessage.setText(message);
        AlertDialog.Builder alert = new AlertDialog.Builder(context);        
        alert.setPositiveButton("OK",listenerOK);
        alert.setView(view);
        alert.show();    
    }
    
    /**
     * 質問メッセージを表示します。
     * @param context
     * @param message
     * @param listenerOK
     */
    public static void showQuestion(Context context,String message,DialogInterface.OnClickListener listenerYes,DialogInterface.OnClickListener listenerNo){
        LayoutInflater inflater = LayoutInflater.from(context);
        final View view = inflater.inflate(R.layout.alert,null);
        ImageView imgAlertIcon = (ImageView)view.findViewById(R.id.imageAlertIcon);
        imgAlertIcon.setImageResource(R.drawable.question);
        TextView lblMessage = (TextView)view.findViewById(R.id.lblMessage);
        lblMessage.setText(message);
        AlertDialog.Builder alert = new AlertDialog.Builder(context);     
        alert.setPositiveButton("YES",listenerYes);
        alert.setNegativeButton("NO", listenerNo);
        alert.setView(view);
        alert.show();    
    }
}

質問メッセージを表示してみます。
public class MainActivity extends Activity {
     public void button1Click(View view) {
         OnClickListener listenerYes = new OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(MainActivity.this, "Yes", Toast.LENGTH_LONG).show();
            }
         };
        AlertHelper.showQuestion(this, "質問メッセージです。", listenerYes , null);
     }
}

2012年9月24日月曜日

Android SDカードに配置したデータベースにアクセスする

SDカードに配置したデータベースにアクセスするには

マニフェストファイルにSD カードのコンテンツの変更/削除の権限を与える設定を行います。


SQLiteOpenHelperを継承したクラスのデータベース名にSDカードのデータベースパスを指定します。
public class DatabaseHelperTest extends SQLiteOpenHelper {

    /* データベース名 */  
    //private final static String DB_NAME = "HelloAndroid.db";  
    private final static String DB_NAME = Environment.getExternalStorageDirectory() + "/HelloAndroid.db"; 
    /* データベースのバージョン */  
    private final static int DB_VER = 1;   
    
    /*  
     * コンストラクタ  
      */  
    public DatabaseHelperTest(Context context) {   
        super(context, DB_NAME, null, DB_VER);   
    }   

    @Override
    public void onCreate(SQLiteDatabase db) {
        // TODO 自動生成されたメソッド・スタブ
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO 自動生成されたメソッド・スタブ
    }

}
あとは従来通りにsqlを発行すればOKです。

Android SDカードに配置したデータベースをデータベースフォルダにコピーする

SDカードに配置したSQLiteデータベースをデータベースフォルダにコピーします。
public class MainActivity extends Activity {

    public void button1Click(View view){
        try {
            final String DB_NAME = "HelloAndroid.db";
            //既存データベースを削除
            this.deleteDatabase(DB_NAME);
            //コピー元パス(SDカード)
            String pathFrom = Environment.getExternalStorageDirectory().getPath() + "/" + DB_NAME;
            //コピー先パス(データベースフォルダ)
            String pathTo = this.getDatabasePath(DB_NAME).getPath();
            //コピー
            FileInputStream fis = new FileInputStream(pathFrom);
            FileChannel channelFrom = fis.getChannel();    
            FileOutputStream fos = new FileOutputStream(pathTo);
            FileChannel channeTo = fos.getChannel();
            try {
                channelFrom.transferTo(0, channelFrom.size(), channeTo);
            } finally {
                fis.close();
                channelFrom.close();
                fos.close();
                channeTo.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
DatabaseHelperのonCreateでやれば良いと思う。

Android SDカードのファイルを読み書きする

実機で実行している場合、「マウント」をOFFします。
(SDカードに書き込みできず、マウントOFFに気づくのに小一時間゜・(ノД`)・゜・)

マニフェストファイルにSD カードのコンテンツの変更/削除の権限を与える設定を行います。
※SDカードから読み込むだけなら必要ありません。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

SDカードは端末によりパスが異なるのでgetExternalStorageDirectory()メソッドでパスを取得します。
String sdPath = Environment.getExternalStorageDirectory().getPath();

あとは普通のファイル入出力と同じです。

SDカードに書き込み
public void button1Click(View view) {
    try {
        //SDカードフォルダのパス
        String sdPath = Environment.getExternalStorageDirectory().getPath();
        //作成するファイル名
        String fileName = "/Hello.txt";
        //書き込み
        BufferedWriter bw = null;
        try {
            FileWriter fw = new FileWriter(sdPath + fileName);
            bw = new BufferedWriter(fw);
            bw.write("ハロー");
        } finally {
            bw.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

SDカードに書き込み
public void button2Click(View view) {
    try {
        //SDカードフォルダのパス
        String sdPath = Environment.getExternalStorageDirectory().getPath();
        //読み込むァイル名
        String fileName = "/Hello.txt";
        //読み込み
        BufferedReader br = null;
        try {
            FileReader fr = new FileReader(sdPath + fileName);
            br = new BufferedReader(fr);
            StringBuilder sb = new StringBuilder();   
            String str;   
            while((str = br.readLine()) != null){   
                sb.append(str +"\r\n");   
            }
            Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
        } finally {
            br.close();
        }
    }catch (Exception e){
        e.printStackTrace();
    }
    
}

2012年9月21日金曜日

.NET OracleデータベースのデータをSQLiteデータベースに移送する

前回、VisualStudioでSQLiteを使用できるようにしました。
.NET VisualStudioでSQLiteを使用する

今回は既存のOracleデータベースの選択したテーブルから、SQLiteにテーブルを作成しデータを移送するコードです。
データ移送がメンドイので作りました。自分用のメモです。

画面を表示するとリストボックスにOracleデータベースのテーブル一覧が表示されます。
SQLiteDB選択ボタンでデータを移送するSQLiteデータベースを選択してください。
データ移送ボタンで処理を実行します。

Imports System.Data.Common

Public Class FrmOracleToSqlite

    Private Const CONNECTION_STRING_PC As String = "user id=xxxxx;password=xxxxx;data source=xxxxx"
    Private Const PROVIDER_NAME_PC As String = "System.Data.OracleClient"

    Private _ConnectionStringAndroid As String = "data source={0}"
    Private Const PROVIDER_NAME_ANDROID As String = "System.Data.SQLite"

    Private Sub Form_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        SetTableNameList()
    End Sub

    Private Sub cmdSelectDb_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdSelectDb.Click
        Using dialog As New OpenFileDialog
            If dialog.ShowDialog() = Windows.Forms.DialogResult.OK Then
                Me._ConnectionStringAndroid = String.Format(Me._ConnectionStringAndroid, dialog.FileName)
                Me.lblDbNm.Text = Me._ConnectionStringAndroid
            End If
        End Using
    End Sub
   
    Private Sub cmdExec_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdExec.Click
        If Me.lstTableName.CheckedItems.Count = 0 Then
            MessageBox.Show("テーブルを選択して下さい。")
            Return
        End If
        System.Windows.Forms.Cursor.Current = Cursors.WaitCursor
        For Each sTableName As String In Me.lstTableName.CheckedItems
            CreateTableForAndroid(sTableName)
        Next
        MessageBox.Show("終了~!")
    End Sub

    '-----Private-----

    Private Sub SetTableNameList()
        Dim factory As DbProviderFactory = DbProviderFactories.GetFactory(PROVIDER_NAME_PC)
        Dim tbl As New DataTable
        Using cnn As DbConnection = factory.CreateConnection()
            cnn.ConnectionString = CONNECTION_STRING_PC
            cnn.Open()
            Try
                Using cmd As DbCommand = cnn.CreateCommand
                    cmd.CommandText = "select table_name from user_tables order by table_name"
                    Using adp As DbDataAdapter = factory.CreateDataAdapter
                        adp.SelectCommand = cmd
                        adp.Fill(tbl)
                    End Using
                End Using
            Finally
                cnn.Close()
            End Try
        End Using 'cnn

        For idx As Integer = 0 To tbl.Rows.Count - 1
            Me.lstTableName.Items.Add(tbl.Rows(idx).Item(0).ToString, False)
        Next
    End Sub
    

    Private Sub CreateTableForAndroid(ByVal sTableName As String)
        Dim factoryPC As DbProviderFactory = DbProviderFactories.GetFactory(PROVIDER_NAME_PC)
        Dim factoryAD As DbProviderFactory = DbProviderFactories.GetFactory(PROVIDER_NAME_ANDROID)

        'PC側テーブル情報を取得
        Dim tblSchema As New DataTable
        Dim tblPKey As New DataTable
        Using cnn As DbConnection = factoryPC.CreateConnection()
            cnn.ConnectionString = CONNECTION_STRING_PC
            cnn.Open()
            Try
                Using adp As DbDataAdapter = factoryPC.CreateDataAdapter
                    '--列情報
                    Using cmd As DbCommand = cnn.CreateCommand
                        adp.SelectCommand = cmd
                        Dim sbSql As New System.Text.StringBuilder
                        With sbSql
                            .AppendLine("select")
                            .AppendLine("   C.Column_Name")
                            .AppendLine("   ,C.Data_type")
                            .AppendLine("   ,C.Data_precision")
                            .AppendLine("   ,C.Data_scale")
                            .AppendLine("   ,C.Nullable")
                            .AppendLine("FROM USER_TAB_COLUMNS C")
                            .AppendLine("   INNER JOIN USER_TABLES T ON")
                            .AppendLine("       C.Table_Name = T.Table_Name")
                            .AppendLine("WHERE")
                            .AppendLine("   T.Table_Name='" & sTableName & "'")
                        End With
                        cmd.CommandText = sbSql.ToString
                        adp.Fill(tblSchema)
                    End Using
                    '--主キー情報
                    Using cmd As DbCommand = cnn.CreateCommand
                        adp.SelectCommand = cmd
                        Dim sbSql As New System.Text.StringBuilder
                        With sbSql
                            .AppendLine("SELECT")
                            .AppendLine("   B.COLUMN_NAME AS COL_NAME")
                            .AppendLine("FROM USER_CONSTRAINTS A")
                            .AppendLine("   LEFT JOIN USER_CONS_COLUMNS B ON")
                            .AppendLine("       B.TABLE_NAME = A.TABLE_NAME")
                            .AppendLine("       AND B.CONSTRAINT_NAME = A.CONSTRAINT_NAME")
                            .AppendLine("WHERE")
                            .AppendLine("   B.TABLE_NAME = '" & sTableName & "'")
                            .AppendLine("   AND A.CONSTRAINT_TYPE = 'P'")
                        End With
                        cmd.CommandText = sbSql.ToString
                        adp.Fill(tblPKey)
                    End Using
                End Using
            Finally
                cnn.Close()
            End Try
        End Using 'cnn

        'Android側にテーブル作成
        '--Drop文作成
        Dim sSqlDrop As String = "DROP TABLE " & sTableName
        '--主キーリスト作成
        Dim lstPKey As New List(Of String)
        For Each r As DataRow In tblPKey.Rows
            lstPKey.Add(r.Item("COL_NAME").ToString)
        Next
        '--Create文作成
        Dim sqlCreate As New System.Text.StringBuilder
        With sqlCreate
            .AppendLine("CREATE TABLE " & sTableName & "(")
            For idx As Integer = 0 To tblSchema.Rows.Count - 1
                If idx <> 0 Then
                    .Append(",")
                End If
                .Append(tblSchema.Rows(idx).Item("Column_Name").ToString)
                Select Case tblSchema.Rows(idx).Item("Data_type").ToString
                    Case "VARCHAR2", "DATE"
                        .Append("   TEXT")
                    Case "NUMBER"
                        If CInt(tblSchema.Rows(idx).Item("data_scale")) = 0 Then
                            .Append("   NUMBER")
                        Else
                            .Append("   REAL")
                        End If
                End Select
                If tblSchema.Rows(idx).Item("Nullable").ToString = "N" Then
                    .Append("   NOT NULL")
                End If
                .AppendLine("")
            Next
            sqlCreate.AppendLine(",PRIMARY KEY (" & String.Join(",", lstPKey.ToArray) & ")")
            sqlCreate.AppendLine(")")
        End With
        '--テーブル作成
        Using cnn As DbConnection = factoryAD.CreateConnection()
            cnn.ConnectionString = Me._ConnectionStringAndroid
            cnn.Open()
            Try
                Using adp As DbDataAdapter = factoryAD.CreateDataAdapter
                    Using cmd As DbCommand = cnn.CreateCommand
                        cmd.CommandText = sSqlDrop
                        Try
                            cmd.ExecuteNonQuery()
                        Catch ex As Exception
                            '何もしない
                        End Try

                        cmd.CommandText = sqlCreate.ToString
                        cmd.ExecuteNonQuery()
                    End Using
                End Using

            Finally
                cnn.Close()
            End Try
        End Using 'cnn

        'データ移送
        Const DATA_CNT As Integer = 100
        Dim iSt As Integer = 0
        Dim iEd As Integer = iSt + DATA_CNT - 1
        '--PC側データのSELECT文の作成
        Dim sqlSelect As New System.Text.StringBuilder
        With sqlSelect
            .AppendLine("SELECT TMPTABLE2.* FROM")
            .AppendLine(String.Format("(SELECT ROWNUM AS ROW_NUM, {0}.* FROM {0} ) TMPTABLE2", sTableName))
            .AppendLine("WHERE ROW_NUM >= :ROW_NUM_Sta")
            .AppendLine("AND   ROW_NUM <= :ROW_NUM_End")
        End With
        Dim cmdSelect As DbCommand = factoryPC.CreateCommand
        cmdSelect.CommandText = sqlSelect.ToString
        Dim pSt As DbParameter = factoryPC.CreateParameter
        pSt.ParameterName = "ROW_NUM_Sta"
        cmdSelect.Parameters.Add(pSt)
        Dim pEd As DbParameter = factoryPC.CreateParameter
        pEd.ParameterName = "ROW_NUM_End"
        cmdSelect.Parameters.Add(pEd)
        '--Android側のInsert文の作成
        Dim lstColNm As New List(Of String)
        Dim lstPrmmNm As New List(Of String)
        For Each row As DataRow In tblSchema.Rows
            lstColNm.Add(row.Item("Column_Name").ToString)
            lstPrmmNm.Add("@" & row.Item("Column_Name").ToString)
        Next
        Dim sqlInsert As New System.Text.StringBuilder
        With sqlInsert
            .AppendLine("INSERT INTO " & sTableName & "(")
            .AppendLine(String.Join(","c, lstColNm.ToArray))
            .AppendLine(")VALUES(")
            .AppendLine(String.Join(","c, lstPrmmNm.ToArray))
            .AppendLine(")")
        End With
        Dim cmdInsert As DbCommand = factoryAD.CreateCommand
        cmdInsert.CommandText = sqlInsert.ToString
        For Each colnm As String In lstColNm
            Dim prm As DbParameter = factoryAD.CreateParameter
            prm.ParameterName = colnm
            prm.SourceColumn = colnm
            prm.SourceVersion = DataRowVersion.Current
            cmdInsert.Parameters.Add(prm)
        Next
        '--データをSelectしInsertをDATA_CNT件づつ繰り返す
        Using cnnPC As DbConnection = factoryPC.CreateConnection()
            cnnPC.ConnectionString = CONNECTION_STRING_PC
            cnnPC.Open()
            Try
                Using cnnAd As DbConnection = factoryAD.CreateConnection()
                    cnnAd.ConnectionString = Me._ConnectionStringAndroid
                    cnnAd.Open()
                    Try
                        cmdSelect.Connection = cnnPC
                        cmdInsert.Connection = cnnAd
                        Do
                            '----Select実行
                            Dim tblSelect As New DataTable
                            Using adp As DbDataAdapter = factoryPC.CreateDataAdapter
                                adp.SelectCommand = cmdSelect
                                cmdSelect.Parameters(pSt.ParameterName).Value = iSt
                                cmdSelect.Parameters(pEd.ParameterName).Value = iEd
                                adp.Fill(tblSelect)
                            End Using
                            If tblSelect.Rows.Count = 0 Then
                                Exit Do
                            End If
                            '----取得したデータの行状態を追加にする
                            For Each row As DataRow In tblSelect.Rows
                                row.SetAdded()
                            Next
                            '----Insert実行
                            Using adp As DbDataAdapter = factoryAD.CreateDataAdapter
                                adp.InsertCommand = cmdInsert
                                adp.Update(tblSelect)
                            End Using
                            '----インクリメント
                            iSt = iEd + 1
                            iEd = iSt + DATA_CNT - 1
                        Loop

                    Finally
                        cnnAd.Close()
                    End Try
                End Using 'cnnAd
            Finally
                cnnPC.Close()
            End Try
        End Using 'cnnPC
    End Sub

End Class

.NET VisualStudioでSQLiteを使用する

Androidで使用するSQLiteデータベースをPC側で作成したいと思います。
少量のデータでしたらAndroid SQLiteのGUIツールで紹介したツールを使えば作成できます。
今回はVisualStudioでSQLiteを使えるようにしたいと思います。

ADO.NET 2.0 Provider for SQLiteをインストール

以下のサイトよりSQLitem-1.0.66.0-setup.exeをダウンロードします。
http://sqlite.phxsoftware.com/
SQLite-1.0.66.0-managedonly-binaries.zip:クラスライブラリのみ
SQLitem-1.0.66.0-setup.exe:setupファイル。IDEへの登録も行う。サーバーエクスプローラなどから使用したければこちらを選択
ダウンロードしたSQLitem-1.0.66.0-setup.exeをインストールします。

VisualStudioからSQLiteを使用する

VisualStudio2008での解説です。
VisualStudioを起動し、プロジェクトを作成します。

サーバーエクスプローラより「データ接続」を右クリックし「接続の追加」を選択。
開いたダイアログのデータソースをSQLiteに変更します。
「NEW...」ボタンで新しいデータベースを作成します。

プログラムからデータベースへアクセスする方法も従来と同じです。
参照設定に「System.Data.SQLite」を追加します。
Private Sub AccessSQLite()
        Const CONNECTION_STRING As String = "data source=C:\Android\Database\HelloAndroid"
        Const PROVIDER_NAME As String = "System.Data.SQLite"

        Dim factory As DbProviderFactory = DbProviderFactories.GetFactory(PROVIDER_NAME)
        Using cnn As DbConnection = factory.CreateConnection()
            cnn.ConnectionString = CONNECTION_STRING
            cnn.Open()
            Try
                Using cmd As DbCommand = factory.CreateCommand
                    cmd.Connection = cnn
                    cmd.CommandText = "CREATE TABLE 省略"
                    cmd.ExecuteNonQuery()
                End Using
            Finally
                cnn.Close()
            End Try
        End Using
    End Sub

2012年9月18日火曜日

Android 実機で実行(Softbank Sharp 003SH)

作ったアプリを実機でデバッグ実行しようと思います。
まずはAndroid端末とPCの接続からです。

ここで私の持っているAndroid端末Softbank Sharp 003SHとWindowsVistaでの手順を紹介します。
端末が古いですが自分用のメモということで。

Android端末とPCをUSB接続できるようにする


003SH用のUSBドライバのインストール
以下のサイトよりファイルをダウンロードしてPCにインストールします。
http://k-tai.sharp.co.jp/support/s/003sh/

SHARP共通 ADB USBドライバのインストール
  1. 以下のサイトよりファイルをダウンロードし解凍します。
    https://sh-dev.sharp.co.jp/android/modules/driver/#adbAll
  2. 端末設定の「USBデバッグ」にチェックを入れ、有効にします。
    (設定 > アプリケーション > 開発 > USBデバッグ)
  3. デバイスとPCをUSBで繋ぐとAndroid Composite ADB Interfaceを認識し、ドライバのインストール画面が起動します。
  4. ドライバ格納先で"\usb_driver_SHARP"を選択し、ドライバをインストールします。

これがなかなか正常にインストールできませんでした・゜・(ノД`)・゜・
まず端末とUSBを接続するとドライバのインストール画面が起動するのですが、ドライバ格納先を選択してもインストールができません。
以下はなんとかインストールできた手順です。

コントロールパネルから「デバイスマネージャ」を起動します。
「ほかのデバイス」に端末が警告付きで表示されています。
警告の内容は「SBM003SH HSUSB Device」がインストールされていないようです。
この端末を右クリックし
「ドライバスフトウェアの更新」

「コンピュータを参照してドライバソフトウェアを検索します」

「コンピュータ上のデバイスドライバの一覧から選択します」

「すべてのデバイスを表示」を選択し「次へ」

「ディスクの使用」を選択し「参照」ボタンでドライバ格納先で"\usb_driver_SHARP"を選択します。

ドライバ格納先の「android-winsb.inf」ファイルを選択し「OK」ボタンをクリックします。

「Android Composite ADB Interface」を選択します。

インストールが開始されますが途中でとまり、WindowsUpdateが起動されるので
PCを再起動しWindowsUpdateを実行します。(どうもここで失敗する)

WindowsUpdateが成功すると、端末とPCがUSBで接続できます。
マイコンピュータを開き、「リムーバルディスク」が表示されていれば端末は認識されています。

Android端末のフォルダをPCから見れるようにする

マイコンピュータに表示されているAndroid端末「リムーバルディスク」ですが、クリックしても「ディスクを挿入してください」と 端末の中を見ることができません。
端末の中のフォルダを見るには、USBとPCを繋いだ状態で、画面上部のステータスバーを下にドラッグします。

表示される通知領域から「USB接続」を選択します。

「USBマスストレージ」画面が表示されるので「マウント」ボタンをクリックします。
これでAndroid端末の中のフォルダが参照できるようになります。


実機でアプリを実行する

まずAndroid端末の設定>アプリケーション>開発>USBデバッグをONに設定します。

次にEclipseを起動し、プロジェクトエクスプローラから実行するプロジェクトを右クリックで「実行」→「実行の構成」を選択します。
「ターゲット」タブを選択し「デバイスを選択するときに常にプロンプトを表示」をチェックONにし「実行」ボタンでダイアログを閉じます。
プロジェクトエクスプローラより実行するプロジェクトを右クリックで「実行」→「Androidアプリケーション」を選択すると 「Androidデバイスの選択」ダイアログが開くので実行するデバイスを選択し、「OK]ボタンで実行します。
Android端末のUSBデバッグをONに設定していないと、ここで接続している端末を認識しません。
これで実機でデバッグ実行できます。

2012年9月13日木曜日

Android AsyncTask#doInBackground内での例外をUIスレッドに通知する

Android AsyncTaskでProgressDialogを表示 その1
Android AsyncTaskでProgressDialogを表示 その2

前回までに作ったProgressBarを表示するAsyncTaskですが
doInBackgroundメソッド内で発生した例外をUIスレッドに通知したいと思います。

バックグラウンドで発生した例外をUIスレッドに通知するにはHandlerクラスを使用します。
詳しい説明や使い方はコチラなどが参考になりました。
愚鈍人 HandlerとMessage - 別スレッドでのGUI操作

ようはUIスレッドでHandlerクラスのインスタンスを生成し、
このインスタンスのsendMessageメソッドでバックグラウンドからUIスレッドに通知できます。
またHandlerクラスのhandleMessageメソッドをオーバライドして、バックグラウンドから通知を受けたときの処理を書きます。

MainActivity.java
ボタンをクリックするとバックグラウンドで処理を実行します。
処理を行うクラス(ReceiveDataクラス)のコンストラクタの引数にHandlerオブジェクトを指定しています。
またHandlerクラスのhandleMessageメソッドをオーバライドして、発生した例外のメッセージを表示します。
※「This Handler class should be static or leaks might occur」と警告が出ますが、今はとりあえず無視します(^^;
package com.example.helloandroid;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
    //Activityが破棄されても、オブジェクトを保持できるようにstaticで宣言する。      
    private static ProgressAsyncTask<Void,Boolean> mTask;  

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //プログレスダイアログを再表示する。      
        if (mTask != null && mTask.getIsShowProgress()){      
            mTask.showDialog();      
        }    

    }

    @Override     
    protected void onPause() {      
        //プログレスダイアログを閉じる。      
        if (mTask != null && mTask.getIsShowProgress()) {      
            mTask.dismissDialog();       
        }      
        super.onPause();      
    }      


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    /**
     * button1クリック処理
     * @param view
     * バックグラウンドで処理を行う
     */
    public void button1Click(View view){
        final Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                Throwable e = (Throwable)msg.obj;
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
            }
        };
        //final MyHandler handler = new MyHandler(this);
        mTask = new ProgressAsyncTask<Void,Boolean>(this){   
            @Override  
            protected Boolean doInBackground(Void... params) {   
                ReceiveData receive = new ReceiveData(mTask,handler);   
                Boolean ret = receive.getMethod1();
                return ret;
            }};   
        mTask.execute();  
    }
}

ProgressAsyncTask.java
プログレスバーを表示するAsyncTaskクラスです。
package com.example.helloandroid;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;

/**
 * ProgressAsyncTask
 * プログレスダイアログを表示し、バックグラウンドで処理を行う抽象クラスです。
 * @param <Param>
 * @param <Result>
 *
 */
public abstract class ProgressAsyncTask<Param,Result> extends AsyncTask<Param,Bundle,Result>{
    private Context mContext;
    private ProgressDialog mProgressDialog;
    private Boolean isShowProgress;

    /**
     * コンストラクタ
     * @param context
     * @param procName
     */
    public ProgressAsyncTask(Context context){
        mContext = context;
    }

    /**
     * getIsShowProgress
     * プログレスダイアログが表示中かどうかを返します。
     * @return
     */
    public Boolean getIsShowProgress() {
        return isShowProgress;
    }

    /**
     * onPreExecute
     * 最初にUIスレッドで呼び出されます。
     * UIに関わる処理をします。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        showDialog();
        isShowProgress = true;
    }

    /**
     * onProgressUpdate
     * doInBackground内でpublishProgressメソッドが呼ばれると、 UIスレッド上でこのメソッドが呼ばれます。 
     * このメソッドの引数の型はAsyncTaskの二つ目のパラメータです。 
     * @param values
     */
    @Override
    protected void onProgressUpdate(Bundle... values) {
        super.onProgressUpdate(values);
        Bundle bundle = values[0];  
        if (bundle.containsKey("Message")){
            mProgressDialog.setMessage(bundle.getString("Message"));
        }
        if (bundle.containsKey("Max")){
            mProgressDialog.setMax(bundle.getInt("Max"));
        }
        if (bundle.containsKey("Progress")){
            mProgressDialog.setProgress(bundle.getInt("Progress"));
        }
    }

    /**
     * onPostExecute
     * doInBackground が終わるとそのメソッドの戻り値をパラメータとして渡して onPostExecute が呼ばれます。
     * このパラメータの型は AsyncTask を extends するときの三つめのパラメータです。
     * バックグラウンド処理が終了し、メインスレッドに反映させる処理をここに書きます。
     * @param result
     */
    @Override
    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * onCancelled
     * cancelメソッドが呼ばれるとonCancelledメソッドが呼ばれます。
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * showDialog
     * プログレスダイアログを表示します。
     */
    public void showDialog() { 
        mProgressDialog = new ProgressDialog(mContext);
        mProgressDialog.setMessage(""); //途中でメッセージを変更するために必要
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setIndeterminate(false);
        mProgressDialog.setCancelable(true);
        mProgressDialog.setButton("キャンセル", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                cancel(true); // 非同期処理をキャンセルする
            } 
        });
        mProgressDialog.show();
    }

    /**
     * dismissDialog
     * ダイアログを終了します。
     */
    public void dismissDialog() {
        mProgressDialog.dismiss();
        mProgressDialog = null;
    }

    /**
     * setProgressMessage
     * プログレスダイアログのメッセージを設定します。
     * @param message
     */
    public void setProgressMessage(String message){
        Bundle data = new Bundle();
        data.putString("Message", message);
        publishProgress(data);
    }
    /**
     * setMax
     * プログレスダイアログの最大値を設定します。
     * @param max
     */
    public void setProgressMax(int max){
        Bundle data = new Bundle();
        data.putInt("Max", max);
        publishProgress(data);
    }
    /**
     * setProgress
     * プログレスダイアログの進捗値を設定します。
     * @param max
     */
    public void setProgress(int progress){
        Bundle data = new Bundle();
        data.putInt("Progress", progress);
        publishProgress(data);
    }
}

ReceiveData.java
実際にバックグラウンドで行う処理です。
ループ変数 i が 7 になったときRuntimeExceptionをスローし、 catch句でHandlerオブジェクトのsendMessageメソッドを実行しています。
package com.example.helloandroid;

import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;

public class ReceiveData {
    
    private ProgressAsyncTask<?,?> mTask;
    private Handler mHandler;
    
    /**
     * コンストラクタ
     * @param task
     * @param handler
     */
    public ReceiveData(ProgressAsyncTask<?,?> task, Handler handler){
        mTask = task;
        mHandler = handler;
    }
    
    /**
     * getMethod1
     
     */
    public Boolean getMethod1(){
        try {
            mTask.setProgressMessage("Method1_Start");
            mTask.setProgressMax(10);
            for(int i=0; i<10; i++){
                mTask.setProgress(i+1);
                SystemClock.sleep(1000);
                if (i == 7) {
                    throw new RuntimeException("エラー! i==7です。");
                }
                if (mTask.isCancelled()) {      
                    return false;                   
                } 
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            final Message msg = new Message();
            msg.obj = e;
            new Thread(new Runnable(){
                public void run() {
                    mHandler.sendMessage(msg);
                }}).start();
            mTask.cancel(true);    
            return false;
        }
    }
}

実行するとUIスレッドでメッセージが表示されます。


さて次は「This Handler class should be static or leaks might occur」と警告が出ないようMainActivityを修正します。
修正方法はコチラを参考に
http://stackoverflow.com/questions/11407943/this-handler-class-should-be-static-or-leaks-might-occur-incominghandler
HandlerクラスをStaticクラスにします。

MainActivity
package com.example.helloandroid;

import java.lang.ref.WeakReference;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
    //Activityが破棄されても、オブジェクトを保持できるようにstaticで宣言する。      
    private static ProgressAsyncTask<Void,Boolean> mTask;  

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //プログレスダイアログを再表示する。      
        if (mTask != null && mTask.getIsShowProgress()){      
            mTask.showDialog();      
        }    

    }

    @Override     
    protected void onPause() {      
        //プログレスダイアログを閉じる。      
        if (mTask != null && mTask.getIsShowProgress()) {      
            mTask.dismissDialog();       
        }      
        super.onPause();      
    }      


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    /**
     * button1クリック処理
     * @param view
     * バックグラウンドで処理を行う
     */
    public void button1Click(View view){
        final MyHandler handler = new MyHandler(this);
        mTask = new ProgressAsyncTask<Void,Boolean>(this){   
            @Override  
            protected Boolean doInBackground(Void... params) {   
                ReceiveData receive = new ReceiveData(mTask,handler);   
                Boolean ret = receive.getMethod1();
                return ret;
            }};   
        mTask.execute();  
    }
    
    /**
     * MyHandlerクラス
     * バックグラウンドで例外発生時の処理
     */
    private static class MyHandler extends Handler {    
        private final WeakReference<MainActivity> mActivity;    
        public MyHandler(MainActivity activity) {        
            mActivity = new WeakReference<MainActivity>(activity);    
        }    
        @Override    
        public void handleMessage(Message msg) {        
            MainActivity activity = mActivity.get();        
            if (activity == null){
                return;
            }
            Throwable e = (Throwable)msg.obj;
            Toast.makeText(mActivity.get(), e.getMessage(), Toast.LENGTH_LONG).show();
        }
    } 
}

2012年9月12日水曜日

Android JSONObjectのNull判定

ちょっぴりハマったのでメモです。

JSONObjectのNull判定は以下のようにします。
if (jsonObject.get(name) == JSONObject.NULL){
    //JsonObjectはnull
}

しらずに「jsonObject.get(name) == null」って書いて小一時間ムダにした(;´Д`A

2012年9月7日金曜日

Android AsyncTaskでProgressDialogを表示 その2

Android AsyncTaskでProgressDialogを表示 その1
進捗を表示しながらバックグラウンドで処理を行うことってよくあるのですが、 まいどまいどAsyncTaskを作っていられないので自分なりの進捗を表示するAsyncTaskを作ってみました。
自分用のメモです。

ProgressAsyncTask
package com.example.helloandroid.tool;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;


/**
 * ProgressAsyncTask
 * プログレスダイアログを表示し、バックグラウンドで処理を行う抽象クラスです。
 * @param <Param>
 * @param <Result>
 *
 */
public abstract class ProgressAsyncTask<Param,Result> extends AsyncTask<Param,Bundle,Result>{
    private Context mContext;
    private ProgressDialog mProgressDialog;
    private Boolean isShowProgress;

    /**
     * コンストラクタ
     * @param context
     * @param procName
     */
    public ProgressAsyncTask(Context context){
        mContext = context;
    }

    /**
     * getIsShowProgress
     * プログレスダイアログが表示中かどうかを返します。
     * @return
     */
    public Boolean getIsShowProgress() {
        return isShowProgress;
    }

    /**
     * onPreExecute
     * 最初にUIスレッドで呼び出されます。
     * UIに関わる処理をします。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        showDialog();
        isShowProgress = true;
    }

    /**
     * onProgressUpdate
     * doInBackground内でpublishProgressメソッドが呼ばれると、 UIスレッド上でこのメソッドが呼ばれます。 
     * このメソッドの引数の型はAsyncTaskの二つ目のパラメータです。 
     * @param values
     */
    @Override
    protected void onProgressUpdate(Bundle... values) {
        super.onProgressUpdate(values);
        Bundle bundle = values[0];  
        if (bundle.containsKey("Message")){
            mProgressDialog.setMessage(bundle.getString("Message"));
        }
        if (bundle.containsKey("Max")){
            mProgressDialog.setMax(bundle.getInt("Max"));
        }
        if (bundle.containsKey("Progress")){
            mProgressDialog.setProgress(bundle.getInt("Progress"));
        }
    }

    /**
     * onPostExecute
     * doInBackground が終わるとそのメソッドの戻り値をパラメータとして渡して onPostExecute が呼ばれます。
     * このパラメータの型は AsyncTask を extends するときの三つめのパラメータです。
     * バックグラウンド処理が終了し、メインスレッドに反映させる処理をここに書きます。
     * @param result
     */
    @Override
    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * onCancelled
     * cancelメソッドが呼ばれるとonCancelledメソッドが呼ばれます。
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * showDialog
     * プログレスダイアログを表示します。
     */
    public void showDialog() { 
        mProgressDialog = new ProgressDialog(mContext);
        mProgressDialog.setMessage(""); //途中でメッセージを変更するために必要
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setIndeterminate(false);
        mProgressDialog.setCancelable(true);
        mProgressDialog.setButton("キャンセル", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                cancel(true); // 非同期処理をキャンセルする
            } 
        });
        mProgressDialog.show();
    }

    /**
     * dismissDialog
     * ダイアログを終了します。
     */
    public void dismissDialog() {
        mProgressDialog.dismiss();
        mProgressDialog = null;
    }

    /**
     * setProgressMessage
     * プログレスダイアログのメッセージを設定します。
     * @param message
     */
    public void setProgressMessage(String message){
        Bundle data = new Bundle();
        data.putString("Message", message);
        publishProgress(data);
    }
    /**
     * setMax
     * プログレスダイアログの最大値を設定します。
     * @param max
     */
    public void setProgressMax(int max){
        Bundle data = new Bundle();
        data.putInt("Max", max);
        publishProgress(data);
    }
    /**
     * setProgress
     * プログレスダイアログの進捗値を設定します。
     * @param max
     */
    public void setProgress(int progress){
        Bundle data = new Bundle();
        data.putInt("Progress", progress);
        publishProgress(data);
    }


}


Activity
ボタンをクリックするとバックグラウンドでgetMethod1()とgetMethod2()を実行します。
import android.app.Activity;

import android.view.Menu;
import android.view.View;

import android.widget.Toast;

import com.example.helloandroid.R;
import com.example.helloandroid.service.ReceiveData;
import com.example.helloandroid.tool.DatabaseHelper;
import com.example.helloandroid.tool.ProgressAsyncTask;



public class MainActivity extends Activity {
    //Activityが破棄されても、オブジェクトを保持できるようにstaticで宣言する。   
    private static ProgressAsyncTask<?,?> mTask;  

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //プログレスダイアログを再表示する。   
        if (mTask != null && mTask.getIsShowProgress()){   
            mTask.showDialog();   
        } 
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    } 

    @Override  
    protected void onPause() {   
        //プログレスダイアログを閉じる。   
        if (mTask != null && mTask.getIsShowProgress()) {   
            mTask.dismissDialog();    
        }   
        super.onPause();   
    }   


    /**
     * button1Click
     * @param view
     * button1クリック時の処理
     * 
     */
    public void button2Click(View view) {
        ProgressAsyncTask<Void,Boolean> task = new ProgressAsyncTask<Void,Boolean>(this){
            @Override
            protected Boolean doInBackground(Void... params) {
                ReceiveData receive = new ReceiveData(MainActivity.this);
                if (receive.getMethod1(this) == false ){
                    return false;
                }
                if (receive.getMethod2(this) == false) {
                    return false;
                }
                return true;
            }};
            mTask = task;
            task.execute();
    }

}

getMethod1()とgetMethod2()メソッドを定義しているクラスです。
package com.example.helloandroid.service;

import com.example.helloandroid.tool.ProgressAsyncTask;

import android.os.SystemClock;


public class ReceiveData {

    /**
     * getMethod1
     * @param task
     */
    public Boolean getMethod1(ProgressAsyncTask<?,?> task){
        task.setProgressMessage("Method1_Start");
        task.setProgressMax(10);
        for(int i=0; i<10; i++){
            task.setProgress(i+1);
            SystemClock.sleep(1000);
            if (task.isCancelled()) {      
                return false;                   
            } 
        }
        return true;
    }

    /**
     * getMethod2
     * @param task
     */
    public Boolean getMethod2(ProgressAsyncTask<?,?> task){
        task.setProgressMessage("Method2_Start");
        task.setProgressMax(20);
        for(int i=0; i<20; i++){  
            task.setProgress(i+1);
            SystemClock.sleep(1000);  
            if (task.isCancelled()) {      
                return false;                   
            } 

        }
        return true;
    }

}

2012年9月4日火曜日

Android AsyncTaskでProgressDialogを表示 その1

AsyncTaskは別スレッドでのバックグラウンド処理とバックグラウンド前後にUIスレッドの操作を行う抽象クラスです。
AsyncTaskを継承するとき実行時引数(Param)、進捗単位(Progress)、処理結果(Result)で扱う[型]を指定します。
実行時引数(Param)はdoInBackgroundメソッドの引数の型になり、
処理結果(Result)はdoInBackgroundメソッドの戻り値の型になります。
進捗単位(Progress)はonProgressUpdateメソッドの引数の型になります。
また、doInBackgroundメソッドの戻り値を引数にonPostExecuteメソッドが呼ばれるので、このメソッドの引数の型は処理結果(Result)の型と同じになります。
public class ProgressTask extends AsyncTask<String, Integer, Boolean>{
    protected Boolean doInBackground(String... params) {}
    protected void onPostExecute(Boolean result) {
    protected void onProgressUpdate(Integer... values) {}
}
型を何も指定しない場合は「Void」を指定します。
public class ProgressTask extends AsyncTask<Void, Void, Void>{
    protected Void doInBackground(Void... params) {}
    protected void onProgressUpdate(Void... values) {}
    protected void onPostExecute(Void result){}
}

AsyncTaskには以下のメソッドがあります。
  • onPreExecute()・・・最初にUIスレッドで呼び出されます。UIに関わる処理をします。
  • doInBackground()・・・ワーカースレッド上で実行されます。ここでバックグラウンドで行う時間のかかる処理をします。
  • onProgressUpdate()・・・doInBackground内でpublishProgressメソッドが呼ばれると、 UIスレッドでこのメソッドが呼び出されます。
  • onPostExecute()・・・doInBackgroundメソッドの処理が終了すると、 UIスレッドで呼び出されます。
  • onCancelled()・・・タスクがキャンセルされると、 UIスレッドで呼び出されます。

AsyncTaskでProgressDialogを表示する際のキモは、以下の2点です。
  1. プログレスダイアログを表示している時にディスプレイの縦横を変更した時の処理
  2. プログレスダイアログをキャンセルした時の処理
とりあえずコードです。
AsyncTaskを継承したProgressTaskクラス
package com.example.helloandroid.service;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.SystemClock;


public class ProgressTask extends AsyncTask<:Void,Integer,Boolean>{
    private Context mContext;
    private ProgressDialog mProgressDialog;
    private Boolean isShowProgress;

    /**
     * コンストラクタ
     * @param context
     */
    public ProgressTask(Context context){
        mContext = context;
    }

    /**
     * getIsShowProgress
     * プログレスダイアログが表示中かどうかを返します。
     * @return
     */
    public Boolean getIsShowProgress() {
        return isShowProgress;
    }

    /**
     * onPreExecute
     * 最初にUIスレッドで呼び出されます。
     * UIに関わる処理をします。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        isShowProgress = true;
        showDialog();
    }

    /**
     * doInBackground
     * ワーカースレッド上で実行されます。
     * このメソッドに渡されるパラメータの型はAsyncTaskの一つ目のパラメータです。
     * このメソッドの戻り値は AsyncTaskの三つ目のパラメータです。
     * @return 
     */
    @Override
    protected Boolean doInBackground(Void... params) {
        for(int i=0; i<:10; i++){
            SystemClock.sleep(1000);
            // キャンセルが押された場合                
            if (isCancelled()) {   
                return false;                
            }
            publishProgress((i+1) * 10);
        }
        return true;
    }

    /**
     * onProgressUpdate
     * doInBackground内でpublishProgressメソッドが呼ばれると、 UIスレッド上でこのメソッドが呼ばれます。 
     * このメソッドの引数の型はAsyncTaskの二つ目のパラメータです。 
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        mProgressDialog.setProgress(values[0]);  
    }

    /**
     * onPostExecute
     * doInBackground が終わるとそのメソッドの戻り値をパラメータとして渡して onPostExecute が呼ばれます。
     * このパラメータの型は AsyncTask を extends するときの三つめのパラメータです。
     * バックグラウンド処理が終了し、メインスレッドに反映させる処理をここに書きます。
     * @param result
     */
    @Override
    protected void onPostExecute(Boolean result) {
        super.onPostExecute(result);
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * onCancelled
     * cancelメソッドが呼ばれるとonCancelledメソッドが呼ばれます。
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * showDialog
     * プログレスダイアログを表示します
     */
    public void showDialog() { 
        mProgressDialog = new ProgressDialog(mContext);
        mProgressDialog.setMessage("データ読み込み中...");
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setIndeterminate(false);
        mProgressDialog.setMax(100);
        mProgressDialog.incrementProgressBy(0);
        mProgressDialog.setCancelable(true);
        mProgressDialog.setButton("キャンセル", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                cancel(true); // 非同期処理をキャンセルする
            } 
        });
        mProgressDialog.show();
    }

    /**
     * dismissDialog
     * ダイアログを終了する。
     */
    public void dismissDialog() {
        mProgressDialog.dismiss();
        mProgressDialog = null;
    }

}
呼び出しもとのMainActivityクラス
package com.example.helloandroid.activity;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;

import com.example.helloandroid.R;
import com.example.helloandroid.service.ProgressTask;

public class MainActivity extends Activity {
    //Activityが破棄されても、オブジェクトを保持できるようにstaticで宣言する。
    private static ProgressTask mTask; 

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);        
        //プログレスダイアログを再表示する。
        if (mTask != null && mTask.getIsShowProgress()){
            mTask.showDialog();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    } 

    @Override
    protected void onPause() {
        //プログレスダイアログを閉じる。
        if (mTask != null && mTask.getIsShowProgress()) {
            mTask.dismissDialog(); 
        }
        super.onPause();
    }

    public void button1Click(View view) {
        //時間のかかる処理を別スレッドで実行
        mTask = new ProgressTask(this);
        mTask.execute();
    }  
}
ダイアログ表示中にディスプレイの縦横を切り替えるとダイアログが消えてしまいます。
これはディスプレイの縦横を変更すると呼び出し元のアクティビティが破棄され、新しい向きのActivityが生成されます。 そのためダイアログも消えてしまうようです。
またAsyncTaskの処理が終了した時点でIllegalArgumentExceptionが発生してしまいます。
これはダイアログのdismissメソッドを呼び出す時に、ダイアログを表示したアクティビティが破棄されているのが原因のようです。
詳しくはこちらに書かれています。
Tech Racho「Android: ダイアログを表示して縦横が変わるとdismissでエラー」
kasa0の部屋「ProgressDialogとAsyncTaskの甘い罠」
対策は
ProgressTaskクラスでダイアログ表示中かどうかの状態を管理します。
MainActivityクラスでProgressTaskオブジェクトをstatic変数で保持します。
MainActivityクラスのonPauseメソッドでプログレスダイアログを閉じます。
MainActivityクラスのonCreateメソッドでプログレスダイアログを表示します。

キャンセル時の処理について
キャンセルボタンクリック時にProgressTask#cancel(true)を呼びます。
doInBackgroundメソッドではキャンセルされたかどうかを見て、処理を行います。
キャンセルされるとonPostExecute()メソッドは呼ばれない為、onCancelledメソッドで終了処理を行います。

2012年9月3日月曜日

Android プログレスダイアログの表示

進捗状況を表示する水平スタイルのプログレスダイアログを表示します。
public void button1Click(View view) {
    ProgressDialog dialog = new ProgressDialog(this);
    //タイトルを設定します。
    dialog.setTitle("Title");
    //メッセージを設定します。
    dialog.setMessage("Message");
    //水平バースタイルに設定します。
    dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    //falseの場合、プログレスバーの進捗状況を表示します。
    dialog.setIndeterminate(false);
    //最大値を設定します。
    dialog.setMax(100);
    //進捗の値を設定します
    dialog.incrementProgressBy(30);
    //セカンダリ値を設定します
    dialog.incrementSecondaryProgressBy(70);
    //キャンセルできるかどうかです。
    dialog.setCancelable(true);
    //ダイアログにキャンセルボタンを追加します。
    dialog.setButton("キャンセル", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            dialog.cancel();
        } 
    });
    //ダイアログを表示
    dialog.show();
}   


処理中かどうかを表示する水平スタイルのプログレスダイアログを表示します。
setIndeterminateメソッドにtrueを設定すると、処理中かどうかを表すプログレスバーになります。
setMax()メソッドやincrementProgressBy()メソッド、ementSecondaryProgressBy()メソッドの設定は無視されるようです。
public void button1Click(View view) {
    ProgressDialog dialog = new ProgressDialog(this);
    //タイトルを設定します。
    dialog.setTitle("Title");
    //メッセージを設定します。
    dialog.setMessage("Message");
    //水平バースタイルに設定します。
    dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    //falseの場合、プログレスバーの進捗状況を表示します。
    dialog.setIndeterminate(true);
    //最大値を設定します。
    dialog.setMax(100);
    //進捗の値を設定します
    dialog.incrementProgressBy(30);
    //セカンダリ値を設定します
    dialog.incrementSecondaryProgressBy(70);
    //キャンセルできるかどうかです。
    dialog.setCancelable(true);
    //ダイアログにキャンセルボタンを追加します。
    dialog.setButton("キャンセル", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            dialog.cancel();
        } 
    });
    //ダイアログを表示
        dialog.show();
}   


円スタイルのプログレスダイアログを表示します。
setProgressStyle()メソッドにProgressDialog.STYLE_SPINNERを設定すると円スタイルになります。
setIndeterminate()メソッドやsetMax()メソッド、incrementProgressBy()メソッド、ementSecondaryProgressBy()メソッドの設定は無視されるようです。
public void button1Click(View view) {
    ProgressDialog dialog = new ProgressDialog(this);
    //タイトルを設定します。
    dialog.setTitle("Title");
    //メッセージを設定します。
    dialog.setMessage("Message");
    //水平バースタイルに設定します。
    dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    //falseの場合、プログレスバーの進捗状況を表示します。
    dialog.setIndeterminate(true);
    //最大値を設定します。
    dialog.setMax(100);
    //進捗の値を設定します
    dialog.incrementProgressBy(30);
    //セカンダリ値を設定します
    dialog.incrementSecondaryProgressBy(70);
    //キャンセルできるかどうかです。
    dialog.setCancelable(true);
    //ダイアログにキャンセルボタンを追加します。
    dialog.setButton("キャンセル", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            dialog.cancel();
        } 
    });
    //ダイアログを表示
    dialog.show();
}