カテゴリー「Android」の39件の記事

2011年6月19日 (日)

レイアウトのXMLに配置したButtonのOnClickがDialogだと使えない?

最近、ButtonのOnClickListenerを実装するのが面倒なので、レイアウトXMLのonClickを利用しているのですが、Dialogでも同じように使おうとしたところ、
以下のメッセージと共に、java.lang.IllegalStateException例外が発生しました。

「Could not find a method buttonClick(View) in the activity」

その時はOnClickListenerを実装した方が早かったので、深く調べなかったのですが、
たまたま思い出したので調べてみました。
※今回はAndroid2.2のソースコードを追いかけました。1.6でも同様の現象に
ぶち当たるのではないかと思います。

実際、レイアウトXMLのOnClickが設定される場所は、Viewクラスにあります。


 

                case R.styleable.View_onClick:
                    if (context.isRestricted()) {
                        throw new IllegalStateException("The android:onClick attribute cannot " 
                                + "be used within a restricted context");
                    }

                    final String handlerName = a.getString(attr);
                    if (handlerName != null) {
                        setOnClickListener(new OnClickListener() {
                            private Method mHandler;

                            public void onClick(View v) {
                                if (mHandler == null) {
                                    try {
                                        mHandler = getContext().getClass().getMethod(handlerName,
                                                View.class);
                                    } catch (NoSuchMethodException e) {
                                        int id = getId();
                                        String idText = id == NO_ID ? "" : " with id '"
                                                + getContext().getResources().getResourceEntryName(
                                                    id) + "'";
                                        throw new IllegalStateException("Could not find a method " +
                                                handlerName + "(View) in the activity "
                                                + getContext().getClass() + " for onClick handler"
                                                + " on view " + View.this.getClass() + idText, e);
                                    }
                                }

                                try {
                                    mHandler.invoke(getContext(), View.this);
                                } catch (IllegalAccessException e) {
                                    throw new IllegalStateException("Could not execute non "
                                            + "public method of the activity", e);
                                } catch (InvocationTargetException e) {
                                    throw new IllegalStateException("Could not execute "
                                            + "method of the activity", e);
                                }
                            }
                        });
                    }
                    break;

handlerName変数には呼び出されるはずのメソッド名が設定されています。

ポイントは、以下のコードになります。

mHandler = getContext().getClass().getMethod(handlerName,
                                                View.class);

この部分で、クラス内にOnClickで設定したメソッドがあるかどうかを確認しています。

さて、今回の現象はActivityだとメソッドが発見できて、Dialogだと発見出来ないわけですから、getContext().getClass()で返されるクラスのインスタンスが違ってそうな気がします。

ということで、Dialog#onCreate(setContentView()を呼び出した直後)で

Log.d("TEST", "ClassName = " + getContext().getClass().getName());

なんてことをしてみました。

すると、出力されたログをみると、

ClassName = android.view.ContextThemeWrapper

と出力されました。

Activityだと同じ事ができないのですが、恐らく、getContext()がActivityのインスタンスを返すのだろうと思われます。

Activityのインスタンスであれば、自分で実装してあれば、メソッドが発見できますが、DialogでgetContext()するとContextThemeWrapperが返ってくるため、

OnClickのメソッドが発見出来ないわけです。

ちなみに、Dialog#getContext()が返す、Contextは、以下の通りです。

 

        mContext = new ContextThemeWrapper(
            context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);

    public final Context getContext() {
        return mContext;
    }

ということで、結論は、「DialogではレイアウトXMLのOnClickは使えない」と言うことがわかりました。

DialogではおとなしくOnClickListenerを実装しましょう。

 

| | コメント (1) | トラックバック (0)

2011年6月 8日 (水)

Activity#finishActivityを解析した

今日、ある方がTwitter上で起動元のActivityを閉じるにはどうするか?
みたいな事をつぶやかれていたので、色々試したことを記述しておくことにする。

で、ドキュメント的にはActivity#finishActivity()を呼び出せば、呼び出し元の
Activityをfinish()する事ができるようだが、実際に1.6 on HT-03Aで動かしてみると
想定した動きにならなかった。

で、finishActivity()のソースコード(v1.6_r2)を見てみると、以下のソースコードだった。

    /**
     * Force finish another activity that you had previously started with
     * {@link #startActivityForResult}.
     * 
     * @param requestCode The request code of the activity that you had
     *                    given to startActivityForResult().  If there are multiple
     *                    activities started with this request code, they
     *                    will all be finished.
     */
    public void finishActivity(int requestCode) {
        if (mParent == null) {
            try {
                ActivityManagerNative.getDefault()
                    .finishSubActivity(mToken, mEmbeddedID, requestCode);
            } catch (RemoteException e) {
                // Empty
            }
        } else {
            mParent.finishActivityFromChild(this, requestCode);
        }
    }

mParentという変数は、呼び出し元Activityと思われるが、この変数に

Activityのインスタンスがセットされていれば、呼び出し元Activityが

無事finishできるようです。

しかし、mParentにインスタンスがセットされるような箇所が存在しませんでした。バグなんですかね…?

ついでに、ドキュメント上、呼び出せと言われているstartActivityForResult()の

ソースコードは以下です。

   /**
     * Launch an activity for which you would like a result when it finished.
     * When this activity exits, your
     * onActivityResult() method will be called with the given requestCode. 
     * Using a negative requestCode is the same as calling 
     * {@link #startActivity} (the activity is not launched as a sub-activity).
     * 
     * 

Note that this method should only be used with Intent protocols      * that are defined to return a result.  In other protocols (such as      * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may      * not get the result when you expect.  For example, if the activity you      * are launching uses the singleTask launch mode, it will not run in your      * task and thus you will immediately receive a cancel result.      *      *

As a special case, if you call startActivityForResult() with a requestCode      * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your      * activity, then your window will not be displayed until a result is      * returned back from the started activity.  This is to avoid visible      * flickering when redirecting to another activity.      *      *

This method throws {@link android.content.ActivityNotFoundException}      * if there was no Activity found to run the given Intent.      *      * @param intent The intent to start.      * @param requestCode If >= 0, this code will be returned in      *                    onActivityResult() when the activity exits.      *      * @throws android.content.ActivityNotFoundException      *      * @see #startActivity      */     public void startActivityForResult(Intent intent, int requestCode) {         if (mParent == null) {             Instrumentation.ActivityResult ar =                 mInstrumentation.execStartActivity(                     this, mMainThread.getApplicationThread(), mToken, this,                     intent, requestCode);             if (ar != null) {                 mMainThread.sendActivityResult(                     mToken, mEmbeddedID, requestCode, ar.getResultCode(),                     ar.getResultData());             }             if (requestCode >= 0) {                 // If this start is requesting a result, we can avoid making                 // the activity visible until the result is received.  Setting                 // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the                 // activity hidden during this time, to avoid flickering.                 // This can only be done when a result is requested because                 // that guarantees we will get information back when the                 // activity is finished, no matter what happens to it.                 mStartedActivity = true;             }         } else {             mParent.startActivityFromChild(this, intent, requestCode);         }     }

すでにmParent変数を参照して動作を振り分けるようになっています。

しかも、mParent変数に値をセットしている場所もない…。

これはどういう事なんだろうか…。

最後にmParent変数へのsetterを見つけたので転載しておきます。

    // ------------------ Internal API ------------------
    
    final void setParent(Activity parent) {
        mParent = parent;
    }

どうやら、setterは非公開APIのようですので、普通にアプリケーションを

作成するためにはfinishActivity()を使って呼び出し元Activityを閉じるという

事はできなさそうです。

| | コメント (0) | トラックバック (0)

2011年5月18日 (水)

職業訓練校のバイト

GWあけてから、職業訓練校でAndroidの授業のチューターのバイトを
することになりました。

専門学校の授業もあるのと、給与面で折り合いがつかなかったので、
専任の講師としては関わらないことにしました。

そこで、今はAndroidの基本からスタートしているのですが、
私もまだまだ基礎ができていない部分があるなと痛感しました。
(実際には問題はタイプミスが多いのですが、そこに到るまでに
苦戦した回数が今日は多かった)
よく見る所は、タイプミスをすぐ発見できるのですが、
例えば、<FrameLayout xmlns:android="http://…
の所のタイプミスは授業時間内に発見できませんでした。

でも、コンパイルは通りますし、実行すると即落ち+
「Binary XML file line #18: You must supply a layout_width attribute」
という、layout_widthが設定出来ていないという意味の例外メッセージが
でるだけです。
この時に、android:layout_width="fill_parent"の記述が
正しい場合は何のエラーなのか???となりました。

ただし、android:layout_widthは必ず指定しなければいけないので
記述がない場合も同様に落ちます。

答えは、xmlns="http;//…"とスキーマの「:」が「;」に
なっていただけでした。
こういう時に原因がすぐに発見できなかったのは痛かったです。

(本当はIDEで自動生成するので本来はそんなに編集するような場所では
ないと思うのですが)

xmlnsの意味などもしっかり押さえないといけないなと思いました。

# 某Ustでantビルドでの周回遅れ勉強会が放送されていますが、
# ああいう所では上記のような状況が発生するのかな?

職業訓練校もバイトという立場での関わりとなりますが、
生徒達の成長を見守りたいと思います。

| | コメント (0) | トラックバック (0)

2011年5月12日 (木)

TextViewに下線を引くには?

TextViewに下線を引く処理をアプリケーションに実装していたのですが、
AutoLink属性を使うことにしましたので、
処理が不要になりました。
しかし、そのまま消してしまうのももったいないので
ここに控えておくことにします。
(XMLからでも設定できたかな…?)

import android.text.Spannable;
import android.text.style.UnderlineSpan;

Spannable t = Spannable.Factory.getInstance().newSpannable(strHandlingUrl);
UnderlineSpan us = new UnderlineSpan();
t.setSpan(us, 0, strHandlingUrl.length(), t.getSpanFlags(us));
txt.setText(t, TextView.BufferType.SPANNABLE);

| | コメント (0) | トラックバック (0)

2011年3月 8日 (火)

第19回日本Androidの会中国支部勉強会に行くことにしました。

3/12に開催される日本Androidの会中国支部勉強会に参加することにしました。

最近は金欠病と仕事の状況もあって、勉強会の遠征は自粛していたのですが、
仕事も大体片付いたので、Androidの会中国支部メンバーの様子も
見ておこうと思ったので。

今回の内容はApp Inventorのハンズオンということで、
支部長自ら教えてくれるようなので、学んできたいと思います。

来週からは、4/2に開催する中国GTUGのネタ作成とPHPの勉強を初めとした
4月からの授業の下準備など、
完成間近(?)の新作アプリのリリースと少しの仕事。。

やることはたくさんありますが、じっくり進めていきたいと思います。

| | コメント (0) | トラックバック (0)

2010年11月15日 (月)

SQLiteをAndroidから利用する時の注意点

中国GTUGの勉強会ででてきて、時間がなかったので、
当日には解決できなかったので、いろいろと調べてみました。

すると、SQLiteOpenHelperがどうやって、SQLiteDatabaseを管理しているのかが
わかりました。

これがわかると、SQLiteOpenHelper#onCreateと
SQLiteOpenHelper#onUpgradeがどんな時に呼ばれるのかが
分かってきます。

結論を言うと、onCreate() or onUpgrade()が呼ばれるのは、アプリケーションが
起動されて、最初にSQLiteOpenHelper#getWritableDatabaseか、
SQLiteOpenHelper#getReadableDatabaseが呼ばれるタイミングで
メソッドが呼ばれることになります。

で、SQLiteDatabaseはSQLiteOpenHelperが管理するのですが、
最初にインスタンスを生成した後、メンバ変数に退避されます。
(戻り値も参照したものを返します)

SQLiteDatabase#getWriableDatabaseの実装が以下のようなものに なっていますので、2回目以降だとメンバ変数の参照を返すように なりますので、closeを呼ぶ時は注意が必要です。

        if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {

            return mDatabase;  // The database is already open for business
        }

特にContentProviderのようにcloseのタイミングが取りにくい(というか、取れない気がする)
場合などは、closeした後、再度SQLiteOpenHelperからSQLiteDatabaseを
取ると、closeされた状態のインスタンスが返される可能性があります。

なので、この時点で考えられる方法としては、

1. closeと一緒にSQLiteOpenHelperにnullを代入する。(再利用時はインスタンス生成する)
2. closeをしない。

上記の2つになると思います。

1.しかないだろ?と思われますが、実は2.でいいのです。
その理由は、SQLiteDatabaseで#finalizeが実装されていて、
ここで、closeとほぼ同じ処理が書かれています。

finalizeはObjectクラスのメソッドですので、
VM(Androidの場合はDalvik-VM)のGC実行時に呼び出されて
無事、close処理が実行されます。

ということから、SQLiteDatabaseも色々考えられている事がわかります。

ただし、Cursor#closeはしっかり呼んだ方がいいです。
これはInterfaceなので、どんな実装になっているかわかりませんので、
明示的に呼ばなければいけません。
closeをしてないと、メモリ圧迫の原因になります。
(LogCatにはたまに(毎回?)警告メッセージが出力されます。)

| | コメント (0) | トラックバック (0)

2010年11月 6日 (土)

AndroidでGree Social Feedbackを利用する(?)

AndroidからGree Social Feedbackを利用してみたいと思ったので
使ってみようと思ったのですが、こういう使い方はありなんだろうか。

例えば、GoogleのサイトをFeedbackさせるとして、、
以下のURLにリクエストすると、確かにGreeのサイトが開き、コメントも
投稿できます。

http://gree.jp/?mode=share&act=write&url=http://www.google.co.jp

例えば、ImageButtonにGreeの画像をはって、ACTION_VIEWのIntentを投げるとか。

本来は、

<iframe src="http://share.gree.jp/share?url=http%3A%2F%2Fdeveloper.gree.co.jp%2Fconnect%2Fplugins%2Fsf&type=0&height=20" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" style="border:none; overflow:hidden; width:100px; height:20px;" allowTransparency="true"></iframe>

こんな感じのコードをHTMLの中に埋め込んで使うようなのですが、

どうなんでしょうか。

| | コメント (0) | トラックバック (0)

SQLiteのautoincrementの値をリセットする方法

私がリリースしているAndroidアプリはSQLiteを利用している事が多くて、
特に、データのリストア機能を使うものについては、
データの入れ替え後にリセットしておくべきだと思います。

ということで、調べてみると、以下のクエリを実行することで
テーブルのautoincrementをクリアする事ができるようです。

update sqlite_sequence set seq=1 where name='テーブル名';

まあ、Androidで登録する件数がintの桁を超える程、「人力で」登録することなんて
ないと思いますけどね。。


| | コメント (0) | トラックバック (0)

2010年9月 2日 (木)

AppWidgetの使い方

今日はAppWidgetの利用方法について整理しておきたいと思います。

まずは、AndroidManifest.xml。

    <receiver android:name="ImageWidget">

        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
                   android:resource="@xml/imagewidgetprovider">
        </meta-data>
    </receiver>
<activity android:name="ImageConfiguration" android:icon="@drawable/icon" android:label="@string/app_name">
    <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"></action>
    </intent-filter>
</activity>

次に、res/xmlディレクトリにappwidget-providerのxmlを作成する。

<appwidget-provider

  xmlns:android="http://schemas.android.com/apk/res/android"
  android:minWidth="146dp"
  android:minHeight="146dp"
  android:initialLayout="@layout/widget"
  android:configure="jp.ievolution.remoteviewsample2.ImageConfiguration"
  >
</appwidget-provider>

android:configureは設定画面が必要であれば追加しておく。

更新タイミングはandroid:updatePeriodMillisで設定可能。

次に設定画面Activityを実装。このサンプルは設定画面で更新間隔を

設定してwidgetを更新させる(つもり)。

package jp.ievolution.remoteviewsample2;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class ImageConfiguration extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {

        Log.d("remoteviewsample2", "ImageConfiguration start");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        EditText txt = (EditText)findViewById(R.id.txtTime);
        txt.setText("0");
       
        Button btn = (Button)findViewById(R.id.btnSet);
        btn.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                EditText txt = (EditText)findViewById(R.id.txtTime);
                int updateRateSeconds = Integer.parseInt(txt.getText().toString());
                int appWidgetId = getIntent().getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
               
                Intent intent = new Intent();
                intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                intent.setData(Uri.parse("imagewidget://update/" + appWidgetId));
               
                PendingIntent newPending = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0);
               
                AlarmManager alarm = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
                alarm.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), updateRateSeconds * 1000, newPending);
               
                Log.d("remoteviewsample2", "alarm set: " + updateRateSeconds + "seconds");
               
                Intent resultValue = new Intent();
                resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                setResult(RESULT_OK, resultValue);
               
                finish();
            }
        });
    }
}

そして、AppWidgetProvider#onUpdateで2枚の画像を切り替える(つもり)。

package jp.ievolution.remoteviewsample2;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.RemoteViews;

public class ImageWidget extends AppWidgetProvider {
   
    @Override
    public void onUpdate(Context context, AppWidgetManager manager, int[] appWidgetIds) {
        Log.d("remoteviewsample2", "onUpdate start");
       
        SharedPreferences preference = context.getSharedPreferences("imagewidget", Context.MODE_PRIVATE);
        boolean bCheck = preference.getBoolean("check", false);
        SharedPreferences.Editor edit = context.getSharedPreferences("imagewidget", Context.MODE_PRIVATE).edit();
        edit.putBoolean("check", bCheck);
        edit.commit();
       
        for(int appWidgetId : appWidgetIds) {
            RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.widget);
            remoteView.setImageViewResource(R.id.image, (bCheck) ? R.drawable.image : R.drawable.no_image);
            manager.updateAppWidget(appWidgetId, remoteView);
        }
        Log.d("remoteviewsample2", "onUpdate end");
    }   
   
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("remoteviewsample2", "onReceive start");
        super.onReceive(context, intent);
        Log.d("remoteviewsample2", "onReceive end");
    }
}

と、こんな感じで、画像は表示できたんだけど、AlarmManagerがうまく動作していないようで、画像が切り替わる様子を見る事はできなかった。

何が悪いのかよく分からないけれども、とりあえずメモ。

(継続して調査しないとわからんですね)

| | コメント (0) | トラックバック (0)

2010年8月31日 (火)

Androidで健康管理のDL数が4000を超えました。

Androidで健康管理(HealthManagerFree)Ver.0.1.0をリリースしてから
半年でやっと累計DL数が4000を超えました。
ありがとうございます。

Androidで健康管理
http://market.android.com/details?id=jp.iEvolution.HealthManager

平均一月で666DL前後で推移していることになります。

Androidで健康管理も機能拡張を続けて今はVer.1.5.2になっていますが、

これからも機能を拡張していく予定ですので今後ともよろしくお願いします。

このアプリはアンドロイダーさんに紹介された以外では、雑誌等での紹介を

される事もないのに、DL数を延ばしています。

頻繁なアップデートを繰り返したという事も無く、要因が不明な感じです。

これまでの活動で分かって来たのが

Androidのユーザがもう少し増えてくれたらもう少し楽になるのかな?

とも思っているのですが、何となく思うのは、

1.アプリの存在を知ってもらう

2.AndroidMarketで上位に表示されるようにする

上記2点をなんとかすればまた違ってくるのかな?と思います。

(皆さん、「そんな事は分かっとるわい!」となるのでしょうが)

| | コメント (0) | トラックバック (0)

より以前の記事一覧