2012年4月30日月曜日

AndroidでGoogleのOAuth2認証を行うには?(外部ライブラリ完全非依存版)

AndroidでGoogleのOAuth2認証を行う必要が出てきた。

今時のアプリではOAuth2が必要な場面は相当多いものの、いかんせん複雑で、認証してやりたいことができるようになるまでのプロセスが非常に長い。

Andorid上では初めて行ったので、できるだけ短く、分かりやすくまとめておこうと思う。

追記12.08.21:以下はWebViewを使用する方法だけど、AccountManagerを使用する方法もある。
琴線探査: AndroidのAccountManager経由でGoogleのOAuth2認証を行うには?(外部ライブラリ完全非依存版)
----

OAuth2認証の流れ

複雑で長いプロセスの場合は、まず大きな流れを掴んでおくことが重要だ。GoogleのOAuth2の流れはこんな感じ。

  1. Googleの「API Console」に自分のアプリを登録
  2. 「API Console」でOAuth2認証で必要な情報を集める(Client ID、Client Secret, Redirect URIs)
  3. 「OAuth 2.0 Playground」を使って許可範囲(scope)を決定する
  4. 集めた情報を使ってWebViewでOAuth2認証ページを表示する
  5. OAuth2認証ページでユーザー自身でアプリケーションからの接続の許可をしてもらう
  6. ユーザー許可後のページタイトルから「code」を取得する
  7. 「code」を使って「アクセストークン」を取得する

このように、アクセストークンを取得することが最終目的となる。

一度アクセストークンを取得出来れば、あとは各種APIにリクエストする時にこのアクセストークンを使えばいい。


Googleのライブラリを使うべきか?

OAuth2の基本的な流れはここに書いてある。

Using OAuth 2.0 to Access Google APIs - Google Accounts Authentication and Authorization — Google Developers

その中で「Google APIs Client Library for Java」という専用のライブラリが紹介されている。

google-api-java-client - Google APIs Client Library for Java - Google Project Hosting

恐らく、これを使うのがスジなのだろうし、使い慣れれば色々と便利なこともあるのだろう。しかし、今回は一切ライブラリを使わないことにした。なぜなら

  • dependenciesがありすぎるので重いし、
  • Android用にdependenciesをクリアしなければならないので面倒だし、
  • Androidでのサンプルが見当たらなかった

から。

必要な処理を書く前に、まずライブラリの使い方から学ばなければならないという、よくあるケースだ。こういうのは正直だるいし、うんざりしている。

直々にHTTPリクエストして直々にjsonレスポンスを処理した方がよほど直感的だと思うので、外部ライブラリに一切依存せずに書くことにした。


「API Console」にてOAuth2認証で必要な情報を集める

コーディングを始める前に、まずOAuth2で必要な情報を集めておく必要がある。

まず「API Console」に逝って、プロジェクトを作成する。ここではプロジェクト名を「OAuth2Google」とした。

プロジェクトを作ると自動的に開く「services」で必要なAPIを「ON」する。今回はユーザーのプロフィール情報を取得するだけだったので何もONしなかった。

「API Access」ページに逝って「Create an OAuth 2.0 client ID」という青くてデカいボタンクリック。

「Product Name」を入力して「Next」。ここでは「OAuth2Google」とした。「Product Logo」は放っておいてよし。

「Application Type」として「Installed Application」を選んで「Create client ID」。

すると、「Client ID」「Client secret」「Redirect URIs」がもらえる。これらがOAuth2で必要な情報だ。



許可範囲(scope)を決定する

OAuth2では、ユーザーからどのAPIに対する許可を得るか(scope)を決めておかなければならない。

OAuth 2.0 Playground」で各APIのURIのリストを確認する。

今回は「Userinfo - Profile https://www.googleapis.com/auth/userinfo.profile」をscopeとして使う。

必要なAPIが複数ある場合は、このようにURIをスペースで区切って記述する。

String scope = "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email";


初期画面を作る


res/layout/main.xmlで、このような単純な初期画面(StartOAuth2GoogleActivity.java)をつくる。


初期画面から認証画面へIntentを投げる

上の初期画面でボタンをクリックするとOAuth2認証画面を表示する。

OAuth2認証画面は汎用性を考えて、OAuth2GoogleActivity.javaとして別アクティビティで定義した。

ボタンクリック時に呼ばれるonClickBtn()では、認証に必要な情報を認証画面に対してIntentで投げている。

// ボタンをクリックした時に呼ばれる
  // OAuth2GoogleActivityに逝く
  public void onClickBtn(View view) {
    Log.v("onClickBtn", "OAuth2開始!");
    Intent intent = new Intent(this, OAuth2GoogleActivity.class);
    intent.putExtra(OAuth2GoogleActivity.CLIENT_ID, CLIENT_ID);
    intent.putExtra(OAuth2GoogleActivity.CLIENT_SECRET, CLIENT_SECRET);
    intent.putExtra(OAuth2GoogleActivity.SCOPE, SCOPE);
    startActivityForResult(intent, OAuth2GoogleActivity.REQCODE_OAUTH);
  }

認証画面をstartActivityForResult()で呼び出して、後で初期画面に戻ってきた時に初期画面に定義してあるonActivityResult()を呼び出してもらうようにする。


認証画面を作る

res/layout/oauth2google.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ViewSwitcher
        android:id="@+id/vs"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

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

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:gravity="center"
                android:orientation="vertical" >

                <ProgressBar
                    android:id="@+id/progBar"
                    style="?android:attr/progressBarStyleLarge"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="5dp" />

                <TextView
                    android:id="@+id/tvProg"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="OAuth2認証ページに接続中..."
                    android:textAppearance="?android:attr/textAppearanceMedium" />
            </LinearLayout>
        </FrameLayout>

        <WebView
            android:id="@+id/wv"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </ViewSwitcher>

</LinearLayout>

OAuth2認証画面は、ViewSwitcherを使ってプログレスバーとWebVIewを交互に表示する。


プログレスバーを表示しつつIntentから認証に必要な情報を得る

認証画面起動時には、まずプログレスバーを表示する。


この時、Intentから認証に必要な情報を取得する。

// Intentからパラメータ取得
    Intent intent = getIntent();
    clientId = intent.getStringExtra(CLIENT_ID);
    clientSecret = intent.getStringExtra(CLIENT_SECRET);
    scope = intent.getStringExtra(SCOPE);


認証ページを表示する

// WebView設定
    wv.setWebViewClient(new WebViewClient() { // これをしないとアドレスバーなどが出る


      @Override
      public void onPageFinished(WebView view, String url) { // ページ読み込み完了時

        // ページタイトルからコードを取得
        String title = view.getTitle();
        String code = getCode(title);

        // コード取得成功ページ以外
        if (code == null) {
          Log.v("onPageFinished", "コード取得成功ページ以外 url=" + url);
          if (!(vs.getCurrentView() instanceof WebView)) { // WebViewが表示されてなかったら
            vs.showNext(); // Web認証画面表示
          }
        }

        // コード取得成功
        else {
          Log.v("onPageFinished", "コード取得成功 code=" + code);
          vs.showPrevious(); // プログレス画面に戻る
          new TaskGetAccessToken().execute(code); // アクセストークン取得開始
        }

      }
    });

    // 認証ページURL
    String url = "https://accounts.google.com/o/oauth2/auth" // ここに投げることになってる
        + "?client_id=" + clientId // アプリケーション登録してもらった
        + "&response_type=code" // InstalledAppだとこの値で固定
        + "&redirect_uri=urn:ietf:wg:oauth:2.0:oob" // タイトルにcodeを表示する場合は固定
        + "&scope=" + URLEncoder.encode(scope); // 許可を得たいサービス

    Log.v("onCreate", "clientId=" + clientId + " clientSecret=" + clientSecret + " scope=" + scope + " url=" + url);

    // 認証ページロード開始
    wv.loadUrl(url);

インテントから取得した認証に必要な情報を使ってURLを構築し、認証ページのロードを開始する。

WebViewに対してセットしてあるWebViewClientのonPageFinished()で、初期ページのロードが完了したらvs.showNext()でプログレスバー画面からWebViewの認証画面に切り替える。

IDとPWを入力していないなら入力を求められる。


すでに入力済みならイキナリこのような画面になる。



コード取得成功ページのタイトルから「code」を抽出する

ユーザーが「アクセスを許可」するとコード取得成功ページに切り替わる。

このページのタイトルは「Success code=XXXXXXX」という風になっていて、ここから「code」の部分をこのように抽出する。

/**
   * 認証成功ページのタイトルは「Success code=XXXXXXX」という風になっているので、
   * このタイトルから「code=」以下の部分を切り出してOAuth2アクセスコードとして返す
   * 
   * @param title
   *          ページタイトル
   * @return OAuth2アクセスコード
   */
  protected String getCode(String title) {
    String code = null;
    String codeKey = "code=";
    int idx = title.indexOf(codeKey);
    if (idx != -1) { // 認証成功ページだった
      code = title.substring(idx + codeKey.length()); // 「code」を切り出し
    }
    return code;
  }

ここで、WebViewに対してセットしておいたWebViewClientのonPageFinished()のコード取得成功ケースの「else」に入る。

そこでvs.showPrevious()で再びプログレスバー表示に切り替えて、TaskGetAccessTokenというAsyncTaskを起動する。


アクセストークンを取得する


内部クラスTaskGetAccessToken

// アクセストークン取得タスク
  protected class TaskGetAccessToken extends AsyncTask<String, Void, String> {
    @Override
    protected void onPreExecute() {
      Log.v("onPostExecute", "アクセストークン取得開始");
      tvProg.setText("アクセストークンを取得中...");
    }


    @Override
    protected String doInBackground(String... codes) {
      String token = null;
      DefaultHttpClient client = new DefaultHttpClient();
      try {

        // パラメータ構築
        ArrayList<NameValuePair> formParams = new ArrayList<NameValuePair>();
        formParams.add(new BasicNameValuePair("code", codes[0]));
        formParams.add(new BasicNameValuePair("client_id", clientId));
        formParams.add(new BasicNameValuePair("client_secret", clientSecret));
        formParams.add(new BasicNameValuePair("redirect_uri", "urn:ietf:wg:oauth:2.0:oob"));
        formParams.add(new BasicNameValuePair("grant_type", "authorization_code"));

        // トークンの取得はPOSTで行うことになっている
        HttpPost httpPost = new HttpPost("https://accounts.google.com/o/oauth2/token");
        httpPost.setEntity(new UrlEncodedFormEntity(formParams, "UTF-8")); // パラメータセット
        HttpResponse res = client.execute(httpPost);
        HttpEntity entity = res.getEntity();
        String result = EntityUtils.toString(entity);

        // JSONObject取得
        JSONObject json = new JSONObject(result);
        if (json.has("access_token")) {
          token = json.getString("access_token");
        } else {
          if (json.has("error")) {
            String error = json.getString("error");
            Log.d("getAccessToken", error);
          }
        }

      } catch (ClientProtocolException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      } catch (JSONException e) {
        e.printStackTrace();
      } finally {
        client.getConnectionManager().shutdown();
      }
      return token;
    }


    @Override
    protected void onPostExecute(String token) {
      if (token == null) {
        Log.v("onPostExecute", "アクセストークン取得失敗");
      } else {
        Log.v("onPostExecute", "アクセストークン取得成功 token=" + token);
        Intent intent = new Intent();
        intent.putExtra(ACCESS_TOKEN, token);
        setResult(Activity.RESULT_OK, intent);
      }
      finish();
    }

  } // END class TaskGetAccessToken

このタスクが起動されると、まずonPreExecute()が呼ばれてプログレスバーの文字表示を「アクセストークンを取得中...」と変更する。

次にdoInBackground()が呼び出される。ここがメイン。トークンの取得はPOSTで送ることになっているので注意が必要。レスポンスはJSONで返ってくるのでJSONObjectに変換してアクセストークンを取得する。

最後にonPostExecute()が呼ばれる。成功なら取得したトークンをIntentに含めて初期画面へ投げる。成功の場合も失敗の場合もとにかくfinish()で初期画面へ戻る。

OAuth2の認証プロセスとしては、ここまでで終了ということになる。


アクセストークンを使ってユーザー情報の取得開始

認証画面から初期画面へ戻ってきたらonActivityResult()が呼ばれるようにしてあるのでこのコードが呼ばれる。

// OAuth2GoogleActivityから戻ってきた時に呼ばれる
  // 認証ができていたらユーザー情報を取得する
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.v("onActivityResult", "OAuth2プロセスから帰還!");
    super.onActivityResult(requestCode, resultCode, data);
    boolean validData = false;
    if (data != null) {
      String token = data.getStringExtra(OAuth2GoogleActivity.ACCESS_TOKEN);
      if (token != null) {
        new TaskGetUserInfo().execute(token);
        validData = true;
      }
    }
    if (!validData) {
      Log.v("onActivityResult", "アクセストークンなし");
      tvName.setText("OAuth2プロセスが正常に行われませんでした");
    }
  }

Intentからアクセストークンを取得する。実際のアプリで使う場合はここでプリファレンスなどに保存することになるだろう。

トークンが取得できたら、ユーザーの名前を表示するためにTaskGetUserInfoというユーザー情報を取得するAsyncTaskを起動する。


ユーザー情報からユーザーの名前を表示する

内部クラスTaskGetUserInfo

// ユーザー情報取得タスク
  protected class TaskGetUserInfo extends AsyncTask<String, Void, JSONObject> {


    @Override
    protected void onPreExecute() {
      Log.v("onPreExecute", "ユーザー情報取得開始");
      tvName.setText("ユーザー情報を取得しています...");
    }


    @Override
    protected JSONObject doInBackground(String... accessTokens) {
      JSONObject userInfo = null;
      String url = "https://www.googleapis.com/oauth2/v2/userinfo" // ここに投げることになってる
          + "?access_token=" + accessTokens[0]; // OAuth2プロセスでもらった
      DefaultHttpClient client = new DefaultHttpClient();
      try {
        HttpGet httpPost = new HttpGet(url);
        HttpResponse res = client.execute(httpPost);
        HttpEntity entity = res.getEntity();
        String result = EntityUtils.toString(entity);
        Log.v("doInBackground", "result=" + result);
        userInfo = new JSONObject(result);
      } catch (ClientProtocolException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      } catch (JSONException e) {
        e.printStackTrace();
      } finally {
        client.getConnectionManager().shutdown();
      }
      return userInfo;
    }


    @Override
    protected void onPostExecute(JSONObject userInfo) {
      if (userInfo == null) {
        tvName.setText("ユーザー情報取得失敗(userInfoがNULL)");
        Log.v("onPostExecute", "ユーザー情報取得失敗 userInfoがNULL");
      } else {
        String name = null;
        try {
          name = userInfo.getString("name");
          tvName.setText("あなたの名前は [" + name + "] ですね?");
          Log.v("onPostExecute", "ユーザー情報取得成功 name=" + name + " userInfo=" + userInfo.toString());
        } catch (JSONException e) {
          tvName.setText("ユーザー情報取得失敗");
          Log.v("onPostExecute", "ユーザー情報取得失敗 userInfo=" + userInfo.toString());
          e.printStackTrace();
        }
      }
    }


  } // END class TaskGetUserInfo


まずonPreExecute()が呼ばれてこのような画面になる。


次にdoInBackground()が呼ばれる。ここでユーザー情報を得るためのAPIにアクセストークンを使ってアクセスしている。

最後にonPostExecute()が呼ばれ、このようにユーザーの名前を表示する。



まとめ

最後にビデオで遷移をまとめ。



あまりのあっけなさに虚しさを禁じ得ない。たったこれだけのために、どんだけ苦労が必要なんだぁ〜!・・・と_| ̄|○

もし似たような事をしようとしている方がいらしたら、是非参考にして頂ければと思います。

Eclipseプロジェクトファイル:OAuth2Google.zip(189KB)

今回は大いにこちらを参考にさせていただきました。

[Android] Android+Twitter4JでOAuthするためのソースコード - adakoda

Twitter4Jを使う場合なら、参考というか、ほぼそのまま使えてしまうのでは?と思う、すばらしいデザインのコードです。

いや、できるだけ短く書くつもりだったのに、結局長かった。

2012年4月28日土曜日

Macでもそろそろウイルス感染が気になりだしたのでSOPHOSを導入。無料。

Macは比較的ウイルスに感染しにくい状況ではある。その理由のひとつは、OSとしてのシェアが低いからだろう。

しかし、iPhone、iPadとの相乗効果でMac OSもシェアが上がってきて、最近ではMacでも感染するウイルスが出たと報道されていた。

もはや安心とは言えない状況っていうか、丸腰ではマズイ状況だ。

以前はClamAVを入れていた。しかし、結構メモリ食いで、イマイチ定義ファイルの更新状況がわかりにくかったこともあって、Lionにしてからは入れてなかった。

そこでちと調べたら、何と老舗のSOPHOSが無料じゃないか。

無償の Mac Anti-Virus、Mac セキュリティと保護 | ソフォス無償ツール

メモリはそれなりに食っている(140MB程度)ようだけど、定義ファイルの更新状況は分かりやすい。


オンアクセススキャナもあるようだし。


しばらく使ってみることにした。

「東京都尖閣諸島寄附金」ホントにやってた。都民じゃないけど、僅かながら寄付したよ。

日経12.04.28朝 尖閣購入 都が専従組織
・・・石原慎太郎知事は・・・沖縄県・尖閣諸島の購入に向けて5月1日付で専従組織を立ち上げると発表・・・購入資金に充てるため都民らから寄付を募るとし、専用の口座を開設したことも明らかにした・・・政府が許可を出さない場合の対応について「下りるまでやる。尖閣を都が守ろうとしているのに、国が許さなかったら理が通らない」・・・

やっぱり尖閣購入のための寄付を募っているらしい。

東京都尖閣諸島寄附金について

本当にやってた!都民じゃないけど、僅かながら寄付した。

こんなこと日本史上はじめてじゃないだろうか。

本来なら、これこそ国が税金でやるべきことだろう。政府がやらないから都が、国民がやろうというのに、政府が許可を出さないなんて意味がわからない。

都知事をはじめ、関係者の方々を応援したい。

日本の領海が大幅に広がったらしい\(^O^)/

日経12.04.28朝 日本の大陸棚拡張 国連認定 沖ノ鳥島周辺など
政府は27日、国連の大陸棚限界委員会が日本最南端の沖ノ鳥島の北方など太平洋の4海域約31万平方キロメートルを日本の大陸棚として新たに認める勧告を採択したと発表した。国連同委の勧告には拘束力がある。国土面積の8割強にあたる海域が新たに認定され、日本はレアメタル(希少金属)や次世代の天然ガス資源であるメタンハイドレートなどの採掘権を主張できる範囲が大幅に広がる。・・・国連海洋法条約・・・政府は条約を批准していない米国などと調整しながら国内手続きを進め、境界を画定。その後、国連に連絡すれば正式に拡張部分の開発権などの効力が生じる。・・・政府は「海洋権益の確保に向けた極めて大きく貴重な第一歩」(内閣官房の担当者)と歓迎・・・

おお?日本の領海が広がったようだ!しかも、国土面積の8割強にあたるくらい!これはすごい。

ヒゲの隊長によると、『「島か岩かの議論は別にやろう」と大陸棚認定議論から切り離した外務省の作戦勝ち』だそうだ。



すばらしいじゃないか外務省。

しかし、何で境界を画定するのにアメリカと相談しなければいけないのかわからない。しかも、アメリカは国連海洋法条約を批准していないらしい。どういうことなんだろ?

しかしこうなってくると、中国がよりムキーってしてきそうだな・・・

エネルギーや資源の面で大幅な国富増となるかもしれないわけだから、あとは中国などにビビらずにちゃんと開発していかないとなぁ。

家庭向け電気料金「7月から3年間10%程度値上げ」。なぜ3年?なぜ10%?根拠を説明して欲しい。

日経12.04.28朝 スピード値上げ高い壁 東電、リストラ強化焦点
・・・「総合特別事業計画」・・・成否のカギを握るのが電気料金の引き上げだ。東電は7月から家庭向けで10%程度上げる構えを示す反面、家庭の反発は必至。結論を急げば拙速との批判が強まる恐れも・・・今回の計画には家庭向けで「7月から3年間10%程度値上げする」方針を明記した・・・家庭向け料金は経済産業省の認可が必要となる・・・枝野幸男経産相は申請を踏まえ「厳しく内容を精査する」と明言・・・「納税者の立場から言うべきことは言う」(安住淳財務相)と牽制する声が上がる・・・

「7月から3年間10%程度値上げする」?

「10%程度」てことは、本当にどれだけ必要なのか計算できていない証拠だろう。「これくらい値上げすれば大丈夫じゃない?」というどんぶり勘定にしか見えない。

「3年間10%」と期限を切っているものの、なぜ3年かわからない。どうせ「やっぱりもっと期間が必要でしたorz」とかいってズルズル10年、20年続くに違いない。

こういうのを簡単に認めてはいけない。なぜ3年?なぜ10%?一人の契約者として、その根拠を納得できるように説明してもらわないと認められない。

実際の権限を持つ枝野経産大臣は今のところ慎重な姿勢を見せているけど、どうだろう。

これからも枝野経産大臣の発言をよーくチェックしとこう。

天皇皇后両陛下は葬儀の簡素化をお望みらしい。国民に負担をかけたくないとお考えなのだろう。

日経12.04.27朝 両陛下、火葬・合葬を希望 簡素化を検討 宮内庁がチーム
宮内庁の羽毛田信吾長官は・・・天皇、皇后両陛下が亡くなられた場合、火葬とする方向で検討を始めると発表した。合葬も検討する。・・・明治、大正、昭和の各天皇は土葬された・・・両陛下は天皇陵の簡素化も望んでいるといい、合葬にすれば、長年連れ添ってきた両陛下が同じ稜に入られることになり、両全体の規模も抑えられるという・・・両陛下から繰り返し言われていた懸案だった・・・

まさか、天皇陛下が死期を悟ってのことなのか??と思ったけどそうではなく、両陛下からずっと言われてきていたことらしい。

両陛下は葬儀を簡素化して、できるだけ国民に負担をかけないようにしたいとお考えなのだろう。

日本の伝統を守り続けながらも、新しい伝統を作ろうともなさっておられる。

何ていうか・・・両陛下はやっぱりすばらしい。

家庭向け電気料金10%値上げ。原発再稼働が条件だから、実際はもっと高くなりそう。値上げしないための知恵が見当たらない。

日経12.04.28朝 東電、7月にも実質国有化 家庭向け10%値上げ 総合計画提出
・・・家庭向け電気料金の10%程度の引き上げや1兆円規模の公的資金による資本注入を盛り込んだ。7月にも実施する方針・・・政府が過半の議決権を握る実質国有化・・・経産相は5月の大型連休明けに計画を認定する見通し・・・次期会長に内定している原子力損害賠償支援機構の下河辺和彦運営委員長・・・家庭向けの値上げについて下河辺氏は「安定供給のためにはしかるべき時点で取り組まざるをえない」・・・新計画は家庭向けの値上げと柏崎刈羽原子力発電所の2013年度からの再稼働を前提にする。10%値上げは一般家庭で月600円程度の負担増につながる見通しだ・・・

議決権が過半になるのは良かったけど、家庭向け電気料金の値上げも同時に通りそうな感じ。

しかも、原発の再稼働が条件で10%値上げだから、再稼働できない場合は10%オーバーになる可能性が大なわけで。

どんなリストラ策や収益改善策をしようとしているのかを見ると、なんかフツーな感じ。「これだけやりますから勘弁して下さい」という感じは受けない。

日経記事より

日本海のメタハイ掘ってきて燃料費を安くして浮いた分で何とかするとか、そういう知恵みたいなものが見当たらない。

消費税と同じで、まず知恵を出そうとせず、足りないんだからみんなで負担すればいいだろう?という単純思考に見える。

東電は本当に知恵を出し尽くしたのか?

枝野経産大臣はこれですんなり通すおつもりなのか?

キングカズ曰く「卒業証書が欲しいのか、勉強がしたいのか。どっちなんだ。」

日経12.04.27朝
小さい頃は「サッカーだけやっていればいい」と思っていた・・・いざブラジルに渡ってみると、サッカーだけで生きるのがどれだけ大変なことか分かった・・・プロになれるのかと不安は募る・・・高校は卒業しておいた方が、とも考えた。そこで教師代わりだった人に諭された。「卒業証書が欲しいのか、勉強がしたいのか。どっちなんだ。大事なのは勉強することだろ。ブラジルまで来て、形だけ高校にいってもしょうがないだろ」・・・見えより実を・・・名刺がJ2でもピッチで輝ける方がいい・・・大学であれトップチームであれ、そこが終着点じゃない。ほんとに大事なのはその「先」。その先に開けている世界が一番大事なはずなんだ。・・・

今でも本当にそう思う。大学なんて、全然終着点じゃない。

では、キングカズが言う「その先」とは一体なんだろう。

きっと一生終着点なんて無いんだと思う。ただ、進むだけ。だとすると、「その先」とは「進み方」のことだろうか。

キングカズのことばで表現すれば、それは「ピッチで輝けること」なんだろうと思う。

自分のことばで表現すれば・・・なんだろう。

驚いたのは、任天堂は上場以来一度も赤字を出していなかったという事実

日経12.04.27朝 任天堂、赤字432億円
任天堂が26日発表した2012年3月期連結決算は、最終損益が432億円の赤字だった。赤字転落は1962年の上場以来で初めて。・・・12年3月期の売上高は前の期比36%減・・・「Wii」の販売台数は前の期比35%減・・・3DSは期初予想から15%少ない1353万台・・・岩田聡社長は・・「たくさんの反省点がある」と述べ、ハードの価格が高かったことや有力ソフトが揃っていなかったことを挙げた・・・「Wii U」は「予定通りに開発が進んでおり、年末商戦に投入できる」という。

岩田社長の反省の弁を聞くと脱力する。ハードが高いことやソフト不足も確かにあるだろうけど、そこは本質ではないと思う。

もう「ゲーム専用機」というカテゴリー自体が壊れてきている、というのがことの本質だと思う。

音楽業界が斉藤和義的に「CDが売れねぇ」と言っていたり、テレビ業界が「視聴率が低い」とか言っているのに似ていると感じる。要するに、人々のライフスタイルが変わったのだ。この変化の流れは止められるものではない。

いや、実は岩田社長もわかってらっしゃるのでは?それを認めたくないだけなのかも。

ただ、さすがだなと驚いたのは、任天堂は上場以来一度も赤字を出していなかったという事実。

この底力は計り知れないが、任天堂にまだこの底力があるのかどうかは「Wii U」で証明されるだろう。

ゲームで、任天堂で育った人間として、何だか寂しいニュースだ。

2012年4月27日金曜日

バックアップ用クラウドストレージは「Google Drive」で決まったわ

これまでバックアップ用のクラウドストレージとしてZumoDriveを使ってきた。しかし、ZumoDriveは今月末で逝くことになった。そこで今月は、様々なクラウドストレージを検討してきた。

琴線探査: ZumoDriveが逝った今、重要データをどのクラウドに預けるべきか?
琴線探査: 新しいSkyDriveで1ファイルあたりの容量が小さい問題は解決したか?

新しいSkyDriveが出てもなお、決定打とはならなかった。そして、噂通りGoogle Driveがリリースされることを期待していた。

そして、ついに出た!

昨日は「まだ準備が整っていない」という話だったけど、今日「整いました」とのメールが来て実際に使ってみた。

ここで、もう一度要求仕様を確認しよう。

琴線探査: 3.11直後の防災の日。重要データをどのクラウドに預けるべきか? | ヤフー、「永久保管」サービス - 日経

要求仕様

  1. 日本国外にデータサーバーがあること
  2. Macでも不便なく利用出来ること
  3. リモートからも利用出来るようにスマートフォンのアプリやWEBページが用意されていること
  4. 基本的にバックアップ用途なのでデータ同期のためにローカルのHD容量が一杯要求されるものはNG
  5. 出来る限り安くて容量が大きいもの

追加仕様1:10GB程度のファイルをアップできること
追加仕様2:アップロード速度が遅くないこと


日本国外にデータサーバーがあること
Googleのクラウドはどこにサーバがあるかわからない。国内にもあるかもしれないが、海外にもあるはず。どうせ各地にバックアップしているはずなので、この点は全く心配していない。

Macでも不便なく利用出来ること
Macでもすでに常駐アプリがあり、問題なく同期できる。

リモートからも利用出来るようにスマートフォンのアプリやWEBページが用意されていること
WEBインターフェースはGoogle Docsがそのまま流用されているので問題ない。Androidアプリはすでにあり、iOSアプリは開発中なので、問題が解決される日はそう遠くないはず。

基本的にバックアップ用途なのでデータ同期のためにローカルのHD容量が一杯要求されるものはNG
同期もできるが、同期しないフォルダを指定することもできる。知る限り、ここまで柔軟な設定ができるサービスをほかに見たことはない。

出来る限り安くて容量が大きいもの
最大16TBまで容量を拡張できるので容量は全く問題ない。価格に関してもZumoやDropboxより安い。

10GB程度のファイルをアップできること
まさに10GBまで可能。今のところはこれで問題なし。
琴線探査: Google Driveの最大ファイルサイズは何GB?

アップロード速度が遅くないこと
まぁ決して速くはないけど、遅くもない。Zumoよりは速い感じ。
琴線探査: Google Driveの速度はどれくらい?有料と無料で速度は変わるのか?

結論

というわけで、Google Driveで決定!待っていたかいがあったね。

しかし、Dropboxなどクラウドストレージをやってきたベンチャーは大変だ。

特に「Insync」なんてモロかぶりじゃないか。
Insync(”GoogleユーザのためのDropbox”)がメジャーVupし無料化

実際、自分はInsyncアンインスコしちゃったし。

Google Drive・・・やっぱり日経の記事になるだけある。クラウドストレージ市場を破壊しかねないサービスだ。

Google Driveの速度はどれくらい?有料と無料で速度は変わるのか?

Google Driveの速度はどれくらいだろう?また、有料と無料で速度は変わるのだろうか?

約2.67GB(2,799,697KB)のファイルを無料状態と有料状態(+25GB)でアップして時間を測ってみた。

無料:約2時間36分(156分 = 9360sec) 2799697KB / 9360sec = 約299 KB/sec
有料:約2時間20分(140分 = 8400sec) 2799597KB / 8400sec = 約333 KB/sec

どちらも大体1GBあたり1時間かかるくらいのスピード。それほど速くもないけど、遅すぎくもないね。

というわけで、どうやら有料だろうと無料だろうと速度は変わらないっぽい。少なくとも、「有料にすれば劇的に速くなる」ということはなさそうだ。

さすがGoogle。太っ腹。

しかしまぁ、有料ユーザーとなった今では、少しは優遇してくれよぉ感も無くはない(^^);

Google Driveの最大ファイルサイズは何GB?

Google Driveで最も気になるのは最大ファイルなんだけど、一体何GB?

Google Docs size limits - Google Docs Help

・・・
Files that you upload but don’t convert to Google Docs format can be up to 10GB each.
・・・

つまり、10GB!

よっしゃ、オケ〜イ!

2012年4月25日水曜日

「原発ゼロ・猛暑で試算すると電力不足」は世論誘導では?なら今年が猛暑かどうか調べてみよう。

日経12.04.24朝 電力不足 西日本で3.6% 今夏全9社、安定供給遠く 原発ゼロ・猛暑で試算
電力9社は23日、今夏の電力需給が東日本では3.7%の余剰、西日本では3.6%の不足となるとの見通しをまとめた。原子力発電所が全て停止し、2010年並の猛暑となった場合の供給余力(予備率)を試算した・・・

日経記事より

この記事は日経の朝刊1面トップ記事。パッと見、「マジかよ」と思った。あたかも「原発が再稼働しないとこの夏は乗り切れない」という印象の記事だ。

しかし、注意しなければならないのはこの試算の条件が「原発ゼロかつ猛暑」であること。

よく考えてみよう。「原発ゼロ」はあり得るけど、「猛暑」はあり得るのか?

何だか知らんが今年の夏は猛暑だと勝手に決めつけてるけど、気象庁の長期予測はどうだろう?

気象庁|季節予報

これを見ると基本的に2010年並の猛暑は無いと考えられる。西日本で平年並みを超えそうな地域で最も確率が高くても40%程度。東日本はほとんど平年並みと考えて良さそうだ。

ラニーニャ現象が終わった今、そうそう猛暑は無いだろうと思っていた。

7月にかけ気温平年並みか高め NHKニュース

となると、この記事はどう考えても世論誘導としか考えられない。国民が気象庁の長期予測を調べないとでも思ったのだろうか。

日経もうそうだし、政府もそうだけど、なんでここまでして原発を再稼働したいのか・・・

こういうのが引っかかるところなんだ。

AndroidでViewのスケーリングをするには?(Honeycomb以前のAPIで)

AndroidでViewのスケーリングをするにはどうしたらいいだろう。

ドキュメントを見ると、setScaleX()とsetScaleY()があるのでこれを使えばいいだろう・・・と思った。

しかし、これはHoneycomb以降の話。API Level 11以降だ_| ̄|○

setRotation()やsetAlpha()もAPI Level 11以降だった。このあたりの「常識だよね?」と思われるAPIは大体Honeycomb以降に追加されたのだとわかった。

では、API Level 11以前でViewを拡大縮小する方法は無いのか?

ここでScaleAnimationの事を思い出した。「実際には」無理かもしれないけど、ScaleAnimationを使えば「見え」だけは拡大縮小するはず・・・と考えてやってみた。





Eclipseプロジェクトファイル:ViewScaling.zip(153KB)

res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/target"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#660000"
        android:gravity="center"
        android:text="Pinch-IN or Pinch-OUT" />

</LinearLayout>

ViewScalingActivity.java
package jp.example;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
import android.view.animation.ScaleAnimation;
import android.widget.TextView;


public class ViewScalingActivity extends Activity {


  protected TextView target;
  protected ScaleGestureDetector sgd;
  protected boolean isPinch = false;
  protected float scalePrev = 1.0f;
  protected float spanPrev = 0.0f;


  @Override
  public void onCreate(Bundle savedInstanceState) {

    // お約束
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // ScaleGestureDetector作成
    sgd = new ScaleGestureDetector(this, new MySimpleOnScaleGestureListener());

    // ターゲット取得
    target = (TextView) findViewById(R.id.target);

  }


  @Override
  public boolean onTouchEvent(MotionEvent event) {
    sgd.onTouchEvent(event);
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:
        procActionUp();
        break;
    } // END switch
    return false;
  } // END onTouchEvent()


  protected void procActionUp() {

    // ピンチ動作だったらスケーリングなし状態までアニメーションさせる
    if (isPinch) {
      ScaleAnimation anim = new ScaleAnimation(scalePrev, 1.0f, scalePrev, 1.0f, target.getWidth() / 2, target.getHeight() / 2);
      anim.setDuration(250);
      target.startAnimation(anim);
    }

    // 各種パラメーターリセット
    isPinch = false;
    scalePrev = 1.0f;
    spanPrev = 0.0f;

  } // END procActionUp()


  protected class MySimpleOnScaleGestureListener extends SimpleOnScaleGestureListener {


    @Override
    public boolean onScale(ScaleGestureDetector detector) {

      // 2つの指の距離を使って遊びを持たせる(一定の距離以下ならピンチとみなさない)
      float spanCurr = detector.getCurrentSpan();
      if (Math.abs(spanCurr - spanPrev) < 20) {
        return false;
      }

      // ピンチ動作とみなす
      isPinch = true;

      // アニメーションが走っていたら止める
      if (target.getAnimation() != null) {
        target.getAnimation().cancel();
      }

      // 現在のスケーリング
      float scaleCurr = detector.getScaleFactor();
      Log.v("onScale", "scalePrev=" + scalePrev + " scaleCurr=" + scaleCurr + " spanCurr=" + spanCurr + " spanPrev=" + spanPrev);

      // スケールアニメーション開始
      ScaleAnimation anim = new ScaleAnimation(scalePrev, scaleCurr, scalePrev, scaleCurr, target.getWidth() / 2, target.getHeight() / 2);
      anim.setDuration(100); // あまり速すぎるとガクガクする
      anim.setFillEnabled(true); // チラつき防止
      anim.setFillAfter(true); // チラつき防止
      target.startAnimation(anim);

      // 各種パラメーター保存
      spanPrev = spanCurr;
      scalePrev = scaleCurr;

      return super.onScale(detector);

    }
  } // END class MySimpleOnScaleGestureListener


} // END class


ポイントはonScale()でチラつき防止のためにScaleAnimationにsetFillEnabled(true)とsetFillAfter(true)しているところ。

API Level 10でも何とかViewのスケーリングができたε-(´∀`*)ホッ

2012年4月24日火曜日

またiPhone 4SもしくはSiriの一面広告。今度はau。

今日、日経にこんな一面広告が。


以前にも似たようなのがあった。

琴線探査: iPhone 4Sの誇りと自信に満ちあふれた一面広告

しかし、前のはソフトバンクバージョンで、今度はauバージョン。

前にソフトバンクバージョンを見た時はソフトバンクの広告だろうと思っていた。

しかし、今回の広告のスタイルがソフトバンクバージョンと同じなので、これはおかしいと思った。

この広告主は一体誰なのか?下の方をよくよく見ると、「Apple Inc All Rights Reserved」の文字が。


ということは、広告主はApple?

何か不思議な感じ。

あす、厚木インターにマクドナルドがオープンするらしい

あす、厚木インターにマクドナルドがオープンするらしい。


この位置だと、すき家側の方だろう。厚木インターに色々とできるのは良い事だ。

ところで今日の日経にも書いてあったが、もはや海老名SAは「ついで」ではなく、ひとつの「目的地」だそうだ。そんな海老名SAに太刀打ちできるものだろうか・・・

まぁそもそも厚木インター周辺の事業は海老名とは関係ないのだろうけど、海老名SAに人出を奪われると厚木インター周辺のお店の人出が奪われることになりはしないだろうか。

そして結果的にお店が潰れていかないかどうか、それが心配だ。

Adobe CS6発売。Flash Builderの扱いはどうなった?

環境移行Q&Aガイド:どのように購入したらいいの? - Adobe Design Magazine

・・・2012年5月には、CS6が発売になります。従来通りの単体・Suiteの製品版や、アップグレード価格ももちろん用意されていますが、CS6からは新たなSuite製品の利用方法として「Adobe® Creative Cloud™ メンバーシップ」がスタートし、クリエイティブに必要なすべての製品やサービスを月額料金で利用していただくことができるようになります。・・・月額5,000円(年間プランの場合)。さらに2012年8月31日までの発売記念版ではCS3以降のいずれかの単体製品もしくはSuite製品をお持ちのお客様に、月額3,000円となる特別価格も用意されています・・・

CS6発売の発表があり、まず気になったのはFlash Builderの扱い。

調べてみると、どうやら一番高いMaster Collectionにのみ含まれるようだ_| ̄|○

その他には含まれていない。

グラフィックデザインソフト | Adobe Creative Suite 6 Design Standard
インタラクティブデザイン | Adobe Creative Suite 6 Design Web Premium
映像編集統合ソフト | Adobe Creative Suite 6 Production Premium

CS5.5 Web Premiumには含まれていたのにぃ。

では、Master Collectionを買うしか無いのか?

「買う」のならそれしか無いだろう。もう一つの選択肢は「Creative Cloud」だ。CCにはFlash Builderが含まれているようだから。

環境移行Q&Aガイド:どのように購入したらいいの? - Adobe Design Magazine

となると、今後FBを入手するには、Master Collectionを買うか、割高なFB単品を買うか、CCに加入するか、ということになる。

となれば、もうCCに加入するしか無いだろぉ〜とまんまとAdobeの戦略に乗らざるを得なくなるわけだ。

確かに、CCに加入すれば全てのアプリが使えるようになるし、8月31日までなら初年度月額3000円にしてくれるなど呼び水もある。

しかし、CS5.5を買ってまだ1年も経っていないのですが・・・

ただ、やっぱり将来的に考えると、CC加入は財務面でもメリットあると思う。

でも、今、今すぐそうするのか?と問われれば、買ったばっかの今は悩ましい。非常に悩ましい。

新しいSkyDriveで1ファイルあたりの容量が小さい問題は解決したか?

SkyDrive、Dropbox風デスクトップ統合機能を実装してWindows、Mac、そしてiOS向けに新アプリケーションをリリース - Tech Crunch Japan

・・・Windows版およびMac版(OS X Lionのみに対応)では、Dropbox風にデスクトップとクラウドを統合して用いることができるようにもなっている・・・Microsoftは無料ディスクスペースを25GBから7GBに縮小した・・・iOS用およびWindows Phone用のモバイル版アプリケーションも新しくなっている。またiPadでもSkyDriveが利用できるようになった。

GoogleのGoogle Driveも間もなくリリースされるのだと噂になっている。・・・

ZumoDriveが逝く日が近い。

琴線探査: ZumoDrive逝ったぁ〜〜!!困ったな(´・ω・`)

そこでGoogle Driveには期待しているのだけど、まだ出ない。ていうか、出るかどうかも定かではないのだが。要するにまだ決定的な選択肢に欠いている。

SkyDriveがMacにも対応したようなので、もう一度チェックしてみることにした。

驚いたのはその安さだ。+50GBで年800円!価格ページが見当たらなかったのでログイン後の容量追加ページのキャプチャ。


Dropboxが+50GBで年99ドル(現レートで約8000円)だから、10倍程度の価格差がある。とにかく、相変わらず安い。

しかし、SkyDriveを採用するのには問題があった。それは、1ファイルあたりの容量が小さすぎること。新しいSkyDriveではどうなのか?

無料の大容量オンラインストレージ SkyDrive - Windows Live on MSN

・・・1 ファイルにつき 300MB* までの写真やドキュメントをオンラインで保存し、相手を選んで共有し、Web ブラウザーからアクセスできます。
*スマートフォンアプリからは 100MB まで・・・

300MBまでにはなったか・・・しかし、GB単位のファイルのバックアップを考えると解決したとは言い難い。

実際にMac用アプリをインストールしてGBレベルのファイルをアップしようとしたら・・・


とういうわけで、やっぱりSkyDriveは選択肢から外さざるを得ない。

---- 追記12.04.25

コメントでwin/macのアプリなら2GBまでアップできるとの情報あり(Thanx匿名さん)

実際に1.9GBのファイルのアップ開始を確認した。1.9GBがOKなら2GBが出来ても不思議じゃない。


ただし、むっちゃ遅いのでアップ完了の確認は断念。

---- 追記ここまで

あぁ〜クマッタ_| ̄|○

Google Driveまだ〜?(・∀・ )っ/凵⌒☆チンチン

究極の無料ストレージ「Google Drive」“5GB無料”で4月25日始動(EXドロイド(エックスドロイド)) - livedoor ニュース

2012年4月23日月曜日

AndroidでBitmapを回転させるには?

AndroidでBitmapを回転させるには?

// ビットマップの幅と高さ
int width = 240;
int height = 480;

// オリジナルのビットマップ作成
Bitmap bmpOrig = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

// 回転マトリックス作成(90度回転)
Matrix mat = new Matrix();
mat.postRotate(90);

// 回転したビットマップを作成
Bitmap bmp = Bitmap.createBitmap(bmpOrig, 0, 0, width, height, mat, true);


createBitmap()を二回使っているけど、1回目と2回目は引数が違うところがポイント。2回目でオリジナルのビットマップを回転させたビットマップを作っている。

この2回目のcreateBitmap()で感心したのは、元の画像を45度で回転させた場合でも描画領域がちゃんと広がっているところ。普通なら切れたりするはず。すばらしい。


ところで、初めに考えたのはImageView.setRotation()を使うことだったのだけど、これがまた・・・API Level 11以上だったので使えなかった_| ̄|○

Androidでテキストを動的に画像化するには?

Androidでテキストを動的に画像化するにはどうしたらいいだろう。

例えば、こんな風。


Eclipseプロジェクトファイル:CreateBitmapText.zip(152KB)


res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#660000" />

</LinearLayout>


CreateBitmapTextActivity.java
package jp.example;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;


public class CreateBitmapTextActivity extends Activity {


  @Override
  public void onCreate(Bundle savedInstanceState) {

    // お約束
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Paint作成
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setColor(Color.WHITE);
    paint.setTextSize(22);

    // 描画テキストの領域を取得
    // rect.widthとmtwは違う。rect.widthの方が小さい。
    // rect.heightとfmHeightは違う。rect.heightの方が小さい。
    String txt = "Bitmap Text : abcdefghijklmnopqrstuvwxyx";
    Rect rect = new Rect();
    paint.getTextBounds(txt, 0, txt.length(), rect);
    FontMetrics fm = paint.getFontMetrics();
    int mtw = (int) paint.measureText(txt);
    int fmHeight = (int) (Math.abs(fm.top) + fm.bottom);
    Log.v("onCreate", "rect.w=" + rect.width() + " rect.h=" + rect.height() + " fm.top=" + fm.top + " fm.bottom=" + fm.bottom + " fm.ascent=" + fm.ascent + "fm.descent=" + fm.descent + " fm.height=" + fmHeight + " mtw=" + mtw);

    // 描画領域ピッタリのビットマップ作成
    int margin = 1; // ギリギリすぎるので上下左右に多少余裕を取る
    Bitmap bmp = Bitmap.createBitmap(mtw + margin * 2, fmHeight + margin * 2, Bitmap.Config.ARGB_8888);

    // ビットマップからキャンバスを作成してテキスト描画
    Canvas cv = new Canvas(bmp);
    cv.drawText(txt, margin, Math.abs(fm.ascent) + margin, paint);

    // ビットマップテキストをImageViewにセット
    ImageView iv = (ImageView) findViewById(R.id.iv);
    iv.setImageBitmap(bmp);

  }

}

第1のポイントは描画領域(ビットマップの大きさ)をいかに計算するか。

描画するテキストの幅・高さをPaintから取得する方法はいくつかあるが、適当だと思われたのは

  • 幅:paint.measureText()のもの
  • 高さ:FontMetricsのtopの絶対値とbottomを足したもの

だった。

上のスクリーンショットで赤い部分は描画領域(ビットマップの大きさ)を表現している。ImageViewのbackgroundの設定で描画していて、ビットマップ自体は透明。


第2のポイントはdrawText()の描画位置はテキストの左下ベースラインだというところ。

これを理解していないとBitmap上の思わぬところに描画して訳がわからなくなるので注意が必要だった。

2012年4月21日土曜日

ジョン・ケージ生誕100年で夏フェスがあるらしい

日経12.04.21朝 生誕100年で記念公演 ケージの音楽 輝き新たに 偶然性重んじ脱西洋を意識
今年は20世紀の音楽や現代アートに大きな影響を与えた米国の作曲家、ジョン・ケージの生誕100年に当たる・・・ケージはコロンビア大学で仏教学者の鈴木大拙から禅の思想を学んでいる。東洋思想に触発されたケージは、作曲家が音楽を厳密に設計する近代西洋的な発想から脱却し、音楽に偶然性を持ち込むという狙い・・・サントリーホール(東京・港)の「サマーフェスティバル」では8月26日、ケージの「ミュージサーカス」を開催・・・パフォーマンス内容は、これから発表する各アーティストに任せる。3.11後の社会状況が反映される内容になりそう・・・(文化部 多田明)

ジョン・ケージ生誕100年なんだぁ。

それで夏にサントリーホールで夏フェスがあるらしい。

ぜひ見てみたいね。8月26日か。φ(..)メモメモ

MacのPicasaアップローダーは廃止だそうだ(´・ω・`)

Spring-cleaning … in spring! | Official Google Blog

・・・
Starting today, the Picasa Web Albums Uploader for Mac and Picasa Web Albums Plugin for iPhoto will no longer be available for download. People can continue to use the uploader and plugin if they are installed. However, we’ll no longer maintain these tools. We strongly encourage people to download Picasa 3.9 for Mac, which includes upload and iPhoto import features.
・・・

最近恒例の「Google春のお掃除」の一環で、MacのPicasaアップローダーとiPhotoのPicasaプラグインは今日からダウンロードできなくなったらしい。

もちろん、今後のアップデートは無し。今後はフルバージョンのPicasaを使ってとさ。

あぁ・・・(´・ω・`)

アップローダーは軽くてよかったのに。Picasaのフルバージョンは機能がありすぎて・・・

ただ、すでにダウンロードしたものは使えるようだ。しかし、いつまで使えるかどうか。今後はPicasaのサイトでアップするようにした方がいいのかも。

2012年4月20日金曜日

「Facebookアカウントへのログインに問題が発生したというご連絡をいただいたため、このメールをお送りしています」 by facebookmail.com。これは釣りなの?

こんなメールが来た。


見た目はFacebookからのメールっぽい。しかし、Facebookにログインの問題を申請した覚えは全くない。あやしすぎる。

と思ってヘッダーを見ると、こんな感じ。

Delivered-To: xxxxxxxxxx@gmail.com
Received: by 10.231.224.26 with SMTP id im26csp2373ibb;
        Thu, 19 Apr 2012 18:29:38 -0700 (PDT)
Received: by 10.68.211.104 with SMTP id nb8mr9029568pbc.40.1334885377970;
        Thu, 19 Apr 2012 18:29:37 -0700 (PDT)
Return-Path: 
Received: from mx-out.facebook.com (outmail022.snc7.facebook.com. [69.171.232.156])
        by mx.google.com with ESMTP id pf9si4505815pbc.356.2012.04.19.18.29.37;
        Thu, 19 Apr 2012 18:29:37 -0700 (PDT)
Received-SPF: pass (google.com: domain of update+zrdozdpehhfe@facebookmail.com designates 69.171.232.156 as permitted sender) client-ip=69.171.232.156;
Authentication-Results: mx.google.com; spf=pass (google.com: domain of update+zrdozdpehhfe@facebookmail.com designates 69.171.232.156 as permitted sender) smtp.mail=update+zrdozdpehhfe@facebookmail.com; dkim=pass header.i=@facebookmail.com
Return-Path: 
DKIM-Signature: v=1; a=rsa-sha256; d=facebookmail.com; s=s1024-2011-q2; c=relaxed/simple;
 q=dns/txt; i=@facebookmail.com; t=1334885377;
 h=From:Subject:Date:To:MIME-Version:Content-Type;
 bh=hxSQs4a25UXm+sYAVsBd2FLLXGpDIf2Lkp+wVbJ0gYE=;
 b=SSoH6sEHK4nY4ItoQkG9gjg3u0G0JQQkKeHLSuZI4OGQxPZWCjE+24o15h8biMPV
 oX7AvowBVbmOXXYFguYOpBn2CxNAdKnlmQPVGbE30lIzICokdyXOQk/UCVs+4mJH
 R46BzAoGJH6cNUkrCk2ZJ4eZ0JKNo741QNcOXqTsWN4=;
Received: from [10.43.9.69] ([10.43.9.69:62095])
 by smout069.snc7.facebook.com (envelope-from )
 (ecelerity 2.2.2.45 r(34222M)) with ECSTREAM
 id B6/45-03997-10CB09F4; Thu, 19 Apr 2012 18:29:37 -0700
X-Facebook: from zuckmail ([MTI3LjAuMC4x]) 
 by www.facebook.com with HTTP (ZuckMail);
Date: Thu, 19 Apr 2012 18:29:37 -0700
To: xxxx xxxx 
From: "Facebook" 
Reply-to: Facebook 
Subject: =?UTF-8?B?RmFjZWJvb2vjga7liKnnlKjjgpLlho3plos=?=
Message-ID: 
X-Priority: 3
X-Mailer: ZuckMail [version 1.00]
Errors-To: update+zrdozdpehhfe@facebookmail.com
X-Facebook-Notify: failed_login; mailid=5fab92fG5af344923eb1G4c0537dG86
X-FACEBOOK-PRIORITY: 1
MIME-Version: 1.0
Content-Type: multipart/alternative;

まず、facebookmail.comはFacebookの所有ドメインのようだ。whoisで調べるとわかる。
FaceBookMail.com - FaceBook Mail

そしてSPFもDKIMも通っている。ということは、単なる送信元詐称ではなく正規のメールサーバーから送信されたはず。

ということは、ひょっとしてFacebookがハックされてるのか?まさかなぁ。

ところで、「ZuckMail」っていうメーラーがあるの?(^^);

facebookmail.com関連では、トレンドマイクロでフィッシングメールと判定されているケースもあるようだ。

Are You Being Phished? | Malware Blog | Trend Micro

しかし、このケースとは内容が違うので、今回のがフィッシングメールかどうかはわからない。

ボタンのリンク先も www.facebook.com だし。

何なんだこれは?とにかく、触らぬ神に祟り無し。

2012年4月19日木曜日

日経社説「都が尖閣を買うのは筋が違う」?それは国が筋を通さないからでしょう?

日経12.04.19朝 日経社説 都が尖閣を買うのは筋が違う
やはり筋が違うのではないか。・・・個人の所有は望ましくない。だからと言って、都が保有すれば済む問題でもない。安全保障は、国が責任を追うべき分野だ。・・・主権を脅かされないよう国が尖閣諸島を所有し、責任をもって守るのが筋・・・都民の税金を使って遠く離れた島を保有して、都民にどんな利点があるのか・・・石原知事は「国が買い上げればいいが、買い上げない。東京が尖閣を守る」・・・野田政権としても尖閣諸島の国有化を検討する姿勢を見せ始めている・・・世界の注目が集まりやすいワシントンで、電撃的にこの話を発表した石原知事のやり方には違和感が残る。

社説の全文はここで読める。

都が尖閣を買うのは筋が違う  :日本経済新聞

大筋では日経の主張する通りだと思う。

本来の筋は、「国が尖閣諸島を所有し、責任をもって守るのが筋」なのだ。

問題は、国がその筋を通さないことだ。

だから石原知事は、わざわざ「世界の注目が集まりやすいワシントンで」表明したのだろう。つまり、国に対して外圧を利用して本来の筋を通すように促したのだと思う。

その結果、実際に「野田政権としても尖閣諸島の国有化を検討する姿勢を見せ始めている」ことになったじゃないか。つまり、石原知事の思惑通りに事は進んでいる。

これは日本にとって、非常に良い事だ。断固支持する。

「都民にどんな利点があるのか」?これは金の問題じゃない。自分は都民じゃないけど、寄付を募るという話もあるらしいので、その時は当然、微力ながらも協力しますよ。


こんな話もある。

痛いニュース(ノ∀`) : 中国“尖閣”に350億円提示!“地権者”実弟が激白 - ライブドアブログ

・・・魚釣島、北小島、南小島を個人所有する地権者の実弟、栗原弘行氏(65)が、石原氏への思いや、350億円を提示した中国関係者の存在、日本政府への不信感などを一気に語った・・・「売却するなら国か自治体」と考えていた栗原家・・・

この話が本当だとすると、中国は尖閣を自国の領土だと言い張りながら、実は自信がなくて買収しようとしていたことになる。

栗原氏が「売却するなら国か自治体」と考えてくれていて良かったよ。栗原氏が金の亡者なら、尖閣はもうとっくに事実上の中国の領土になっていただろう。こんなことがあっていいはずがない。

日経は石原知事を批判する前に、まず国の姿勢を批判するべきだ。

ミサイル発射で危機的状況だというのに「電話したもののつながらなかった」?話にならないね。

日経12.04.19朝 官房長官への電話つながらず ミサイルで防衛相答弁
田中直紀防衛相は18日の衆院予算委員会で、・・・藤村修官房に情報を伝えようと電話したもののつながらなかったことを明らかにした・・・

ミサイル発射で危機的状況だというのに「電話したもののつながらなかった」?使った手段は普通の携帯電話か?

話にならないね。

ちょっきもちょっきだけど、官房長官も官房長官だ。こういう時くらい、必ず連絡がつくチャンネルをオープンにしておかないとダメだろう。

万が一、今、日本が戦争になったら、自衛隊がいかに優秀だろうと日本は負けるだろう。

民主党政権では日本を守れない。そう強く感じた。

大飯原発再稼働に関して京都府と滋賀県の知事がまとめた提言を首相は「そんなの関係ねえ」と言っているようだ・・・

日経12.04.19朝 提言は再稼働条件とせず

■首相 野田佳彦首相は18日の参院予算委員会で、福井県の関西電力大飯原発3,4号機の再稼働をめぐり、京都府と滋賀県の両知事がまとめた政府への7項目の提言は再稼働の前提条件にはならないとの考えを示した。

2cm×3cm程度のちぃちゃな記事。

しかし、これってつまり、首相は国民が何と言おうと「そんなの関係ねえ」と言っていることに等しいのでは?

7項目の提言というのがよっぽど厳しいのだろうけど、こんな態度では到底国民の理解は得られないだろうなぁ。

やっぱり、「原発は一瞬ゼロになる」のだろう。

確かに多大なリスクはあるけど、これはある意味、壮大で世界的にも意義のある社会実験と言えると思う。

試しに一回やってみようじゃないか。この程度の再稼働運動だから、多分大丈夫だと思う。なぜなら、ホントにまずいならもっと必死にやるはずだから。いや、どうかなぁ・・・(^^);

今後日本が再び原発を使うかどうかは、今年の日本人の節電努力次第だと思う。多くの日本人は、どちらを選ぶだろうか。

AndroidのTranslateAnimationでリアルに(Property Animationのように)アニメーションするには?

昨日はAndroid上のTranslateAnimationのクセについて学び、無事ターゲットをリアルに(Property Animationのように)アニメーションしながら動かすことができた。

琴線探査: AndroidのTranslateAnimationのクセはひどいね

昨日のコードのmoveTarget()メソッドは相対座標系に対する移動だった。いわばmoveRelative()とでも呼ぶべきものだった。そこで今度は親コンテナの座標系に対する移動をやってみた。

「target」と表示されている赤い四角が、画面上でタップした場所に移動するというサンプル。





Eclipseプロジェクトファイル:TransformAnimationMoveTo.zip(192KB)

res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/parent"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/target"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#660000"
        android:gravity="center"
        android:text="target"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>


TransformAnimationMoveToActivity.java
package jp.example;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;


public class TransformAnimationMoveToActivity extends Activity {


  // 移動アニメーションさせるターゲット
  protected View target;

  // ターゲットの親コンテナ
  protected View parent;


  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    parent = findViewById(R.id.parent); // 親コンテナ取得
    target = findViewById(R.id.target); // ターゲット取得
  } // END onCreate()


  @Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:

        // 親コンテナの画面に対する描画位置オフセットを計算
        Display disp = getWindowManager().getDefaultDisplay();
        int offsetX = disp.getWidth() - parent.getWidth();
        int offsetY = disp.getHeight() - parent.getHeight();

        // 移動先を計算(オフセットとターゲットの中心点を考慮)
        int toX = (int) (event.getX() - offsetX - target.getWidth() / 2);
        int toY = (int) (event.getY() - offsetY - target.getHeight() / 2);

        // タッチアップでターゲット移動
        moveTo(toX, toY);

        Log.v("onTouchEvent", "ACTION_UP x=" + target.getLeft() + " y=" + target.getTop() + " offsetX=" + offsetX + " offsetY=" + offsetY + " toX=" + toX + " toY=" + toY);

        break;
    } // END switch
    return super.onTouchEvent(event);
  } // END onTouchEvent()


  /**
   * ターゲットを指定の場所に移動する
   * 
   * TranslateAnimationで移動アニメーションをすると、 見かけ上は移動したように見えるが実際には移動していないらしい。
   * そこで、アニメーション完了時にlayout()を使って物理的にも移動させる。
   * 
   * @param x
   *          X軸に対する移動先
   * @param y
   *          Y軸に対する移動先
   */
  protected void moveTo(int x, int y) {
    if (target.getAnimation() != null) {
      return; // アニメーション中なら何もしない
    }
    final int left = target.getLeft();
    final int top = target.getTop();
    final int toX = x; // AnimationListenerから参照できるように
    final int toY = y; // AnimationListenerから参照できるように
    TranslateAnimation anim = new TranslateAnimation(left, toX, top, toY);
    anim.setAnimationListener(new Animation.AnimationListener() {

      @Override
      public void onAnimationStart(Animation animation) {
      }

      @Override
      public void onAnimationRepeat(Animation animation) {
      }

      @Override
      public void onAnimationEnd(Animation animation) {
        target.layout(toX, toY, toX + target.getWidth(), toY + target.getHeight()); // 物理的にも移動
        target.setAnimation(null); // これをしないとアニメーション完了後にチラつく
        parent.invalidate(); // 後ろに残る残像?ゴミ?をクリアする(実行環境によるみたい)
      }

    });
    anim.setDuration(500); // セットしないとアニメーションしない
    target.layout(0, 0, target.getWidth(), target.getHeight()); // 初期位置に戻す。これをしないと2度目以降のアニメーションがおかしくなる(チラつく)
    target.startAnimation(anim);
  } // END moveTo()


} // END class TransformAnimationMoveToActivity

第1のポイントは、moveTo()で引数として取得したx、yをそれぞれfinal変数のtoX、toYに格納しているところ。こうしないとAnimationListenerから参照できない。

第2のポイントは、new TranslateAnimation(left, toX, top, toY)。相対座標系での移動の場合と違って特に何を計算するでもなく単にtoX、toYを与えている。

ポイントというほどのことではないが、onTouchEvent()でActivityに対するMotionEventを取得しているので、上の方のバーやらなんやらで実際の表示領域(ターゲットの親コンテナ)の座標系と合わないため、画面サイズと親コンテナサイズの差分を取ってオフセット計算した。

2012年4月18日水曜日

AndroidのTranslateAnimationのクセはひどいね

まず、AndroidでViewを移動させようとして困った。

getX()とかsetX()とか無いし。move()とかも無いし。じゃあどうするの?といったら、どうやらlayout(int left, int top, int right, int bottom)で移動させるらしい。('A`)マンドクセじゃないかよぉ。とは言え、これはちゃんとできたからよし。

問題は移動アニメーション(TranslateAnimation)だ。何だか移動アニメーションさせると元の位置に戻るという変態的な動きをするのはなぜだ??

調べてみると困っている人多数らしく、animation.setFillAfter(true)すればいいという声が多数。

Android TranslateAnimation animation - Stack Overflow

・・・
By default, the animation resets the object to the initial state. You need to specify fillAfter to true:

animation.setFillAfter(true);
・・・

そこで試してみたが問題は解決しなかった。移動アニメーションさせた後でタッチ操作でターゲットを移動させると、残像が残っている!何なんだコレは?

アニメーション後のターゲットオブジェクトの座標を調べてみると、全く動いていない!どうやらsetFillAfter()はアニメーション終了時に「ビジュアル」だけ残すというメソッドらしい。こんなの意味あんのかよぉ〜(^^);

さらに調べていくと、Androidの開発者による決定的な記事が見つかった。

Animation in Honeycomb | Android Developers Blog

・・・
Animation Prior to Honeycomb
・・・
Finally, the previous animations changed the visual appearance of the target objects... but they didn't actually change the objects themselves. You may have run into this problem.
・・・
The problem is that the animation changes where the button is drawn, but not where the button physically exists within the container
・・・
Property Animation in Honeycomb・・・ValueAnimator・・・ObjectAnimator
・・・

全くその通り。そして、その問題はHoneycomb(API Level 11)以降に追加されたValueAnimatorやObjectAnimatorによって解決されたらしい。

要するに、AndroidはGingerbread(API Level 10)でもまだ未完成だったということだと思う。Flashではアニメーションのことをいちいち「Property Animation」だ何だと区別しない。それが常識だから。やっぱりAndroidはICSでやっと完成されたOSになったのだろうなと感じた。

しかし、解決されてよかったね!とはならない。今、稼働しているAndroid機のほとんどはHoneycombレベル以下だから、ValueAnimatorとか使えないわけ。


そこで何とかすることにした。要するに、アニメーションが終わった後にターゲットを物理的に移動させればいいわけだ。ということでやってみたのがこんな風。


Eclipseプロジェクトファイル:TransformAnimation.zip (153KB)


res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/target"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#660000"
        android:gravity="center"
        android:text="target"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>


TransformAnimationActivity.java

package jp.example;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;


public class TransformAnimationActivity extends Activity {


  // 移動アニメーションさせるターゲット
  protected View target;


  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    target = findViewById(R.id.target); // ターゲット取得
  } // END onCreate()


  @Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:
        Log.v("onTouchEvent", "ACTION_UP x=" + target.getLeft() + " y=" + target.getTop());
        moveTarget(50, 50); // タッチアップでターゲット移動
        break;
    } // END switch
    return super.onTouchEvent(event);
  } // END onTouchEvent()


  /**
   * ターゲットを相対的に移動する
   * 
   * TranslateAnimationで移動アニメーションをすると、
   * 見かけ上は移動したように見えるが実際には移動していないらしい。
   * そこで、アニメーション完了時にlayout()を使って物理的にも移動させる。
   * 
   * @param dx X軸に対する相対移動量
   * @param dy Y軸に対する相対移動量
   */
  protected void moveTarget(int dx, int dy) {
    if (target.getAnimation() != null) {
      return; // アニメーション中なら何もしない
    }
    final int left = target.getLeft();
    final int top = target.getTop();
    final int toX = left + dx;
    final int toY = top + dy;
    TranslateAnimation anim = new TranslateAnimation(left, toX, top, toY);
    anim.setAnimationListener(new Animation.AnimationListener() {
      @Override
      public void onAnimationStart(Animation animation) {
      }
      @Override
      public void onAnimationRepeat(Animation animation) {
      }
      @Override
      public void onAnimationEnd(Animation animation) {
        target.layout(toX, toY, toX + target.getWidth(), toY + target.getHeight()); // 物理的にも移動
        target.setAnimation(null); //これをしないとアニメーション完了後にチラつく
      }
    });
    anim.setDuration(500); // セットしないとアニメーションしない
    target.layout(0, 0, target.getWidth(), target.getHeight()); // 初期位置に戻す。これをしないと2度目以降のアニメーションがおかしくなる(チラつく)
    target.startAnimation(anim);
  } // END moveTarget()


} // END class TransformAnimationActivity

重要な部分はmoveTarget()に集まっている。

第1のポイントは、アニメーションにAnimationListenerをセットしてアニメーション終了時のイベント取得して、そこで物理的にターゲットを移動する部分。

第2のポイントは、チラつき防止のためにターゲットを初期位置に戻す部分。チラつき方が何となくTransform Matrix関連っぽい感じだったので、とりあえず初期位置に戻してみたらどう?と思ってやってみたらうまくいったみたい。

---- 追記12.04.19

第3のポイントは、onAnimationEnd()でtarget.setAnimation(null)することだとわかった。これをしないとアニメーション完了後にチラつく。チラつきを見る前にこのコードを入れていたので気が付かなかったようだ。

---- END OF 追記

オブジェクトごとにいちいちこんなコードを書くようではたまらんので、どうにかして便利なクラスを書かないとダメだね。こりゃ。

2012年4月14日土曜日

3.11後は「コタツでミカン食べながらラッパーを夢見る話はもう成立しない」。ならどんな話なら成立する?「サイタマノラッパー」続編「ロードサイドの逃亡者」に期待したい。

日経12.04.13夕 SR サイタマノラッパー ロードサイドの逃亡者
ラップにのせて関東一円、青春地獄めぐりのように展開する、本来の意味でのピカレスク。低予算なのに、非常に規模の大きな、群集シーンの空間を見事に演出しているのにビックリ。入江悠監督。★★★★(宇田川幸洋)

サイタマノラッパーの続編が近く公開されるらしい。

SRサイタマノラッパー ロードサイドの逃亡者



以前、宇多丸先生が絶賛ということで見てみた。正直、微妙な感じだったが(^^); しかし、それなりの味わいはあるなぁとも感じた。

★4つというのもなかなかの評価だし。

と、ここまでの情報だったら、さらに興味を持ったり、記事を書いたりまではしなかっただろう。なぜ記事を書こうと思ったかというと、この前の日に入江監督のインタビュー記事が載っていたから。


日経12.04.12夕 入江悠さん「サイタマノラッパー」シリーズ監督 混戦状態の今はチャンス
・・・野外ステージのシーン・・・2000人を動員・・・「エキストラにも俳優並みの演技を要求したので、緊張感はすごかった」埼玉県深谷市の実家近くの空き地にセットを組んだ。・・・自主制作で始めたが、撮影中に出資者が次々と手を挙げた・・・「SR」は「最後の映画のつもりで原点を見つめた。語るべき物語がない土地で、何かを語りたいやつらはどうしたらいいのかと」・・・難渋する脚本が一気に書けたのは震災後。「現実がフィクションを超えてしまった」・・・「コタツでミカン食べながらラッパーを夢見る話はもう成立しない」・・・

3.11は物理的な面だけでなく、精神的な面でも日本人に大きな影響を与えているんだなぁと改めて思う。

3.11後は「コタツでミカン食べながらラッパーを夢見る話はもう成立しない」のだという。なら、どんな話なら成立するのか?

SR3にその答えがあるのだろう。期待したい。

YouTubeで新報道2001が無料配信されるらしい

日経12.04.13朝 フジテレビの番組 スマホで ユーチューブ無料配信
フジテレビジョンは来週から米グーグルのインターネット動画サイト「YouTube(ユーチューブ)」で、テレビ放送したアニメや報道、娯楽番組を無料配信する。まずは1日平均2時間程度を配信。テレビ広告が減少する中、ネット広告でも稼ぐ。本格的な無料配信は国内放送局で初めて。・・・報道番組では「FNNニュース」を毎日数十本ずつ配信するほか、「新報道2001」を本放送から数時間後に配信する。・・・無料配信すると、テレビの視聴者数やテレビ広告の価値に影響するとして、広告主が懸念する可能性もある。だが、フジテレビは強力なコンテンツをネットでも配信することで、テレビ番組の認知度が高まると期待している・・・

これからは新報道2001がYouTubeにアップされるらしい。これはいいね。

フジテレビは「テレビ対ネット」の意識をいち早く捨てて、むしろネットを味方につけることにしたらしい。その方が賢いと思う。そもそも民放は広告で賄われているわけだから、ネット広告で稼ぐことだって当然考えるべきことだろう。

ただ実際は、もはやネットで無料配信しなければならないほどテレビ自体の衰退が激しくて、やむにやまれずという感じなのかもしれないけど。

YouTubeとしても願ったり叶ったりの流れだろう。最近YouTubeはプロのコンテンツを呼び込むのに熱心なようだから。

今後、他の民放が追随するかどうか。もしそうなれば、かなり便利になるのでは。

2012年4月13日金曜日

Androidでアニメーションの開始・経過・終了のイベントを取得するには?

Androidでアニメーションの開始・経過・終了のイベントを取得するには?

こんな風。

Animation anim = new Animation();
anim.setAnimationListener( new Animation.AnimationListener() {

  @Override
  public void onAnimationEnd(Animation animation) {
  }

  @Override
  public void onAnimationRepeat(Animation animation) {
  }

  @Override
  public void onAnimationStart(Animation animation) {
  }
    
});

Animationクラスの内部にAnimationListernerというインターフェースがあるので、これを実装してAnimationにリスナーとしてセットする。

インターフェースなので、1つのメソッドしか必要なかったとしても3つのメソッドをすべて実装しなくてはならないのが面倒だよなぁ・・・というわけで、アダプタークラスを作っておくと楽できる。

package ex.daix;

import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;

public class AnimationListenerAdapter implements AnimationListener {

  @Override
  public void onAnimationEnd(Animation animation) {
  }


  @Override
  public void onAnimationRepeat(Animation animation) {
  }


  @Override
  public void onAnimationStart(Animation animation) {
  }

}

このクラスを作っておくと、上のコードはこういう風にできる。

Animation anim = new Animation();
anim.setAnimationListener( new AnimationListenerAdapter() {

  @Override
  public void onAnimationStart(Animation animation) {
  }
    
});

これで大分コードが短くなるはず。

AndroidでアニメーションをXMLで定義してViewにセットするには?

AndroidでアニメーションをXMLで定義してViewにセットするにはどうしたらいいだろう?

こんな風。

Animation anim = (Animation) AnimationUtils.loadAnimation(this, R.anim.zoomout_fadeout);
view.startAnimation(anim);

ここで、アニメーションは res/anim/zoomout_fadeout.xml で定義されている。

アニメーションをプログラム内で定義せずXMLで定義するメリットはなんだろう。

  • 各プログラムで共有できる
  • パラレルで動くようなアニメーションを定義しやすい
  • コードがシンプルになる

というところだろうか。

2012年4月11日水曜日

Android 4.0.3 rev.2のエミュでGPUエミュレーションを使って高速化するには?

先日、SDK Toolsと4.0.3のエミュがアップデートした。

琴線探査: AndroidのICS(4.0.3)エミュがバージョンアップ。GPUサポートで高速化?SDK Toolsもrev.18にアップデート。

この時の目玉は4.0.3エミュがGPUをサポートしたことよってグラフィックスが高速化される話だったと思う。しかし、自分が試した限りではちっとも速くなった感じがしなかった(^^);

上の記事のコメントで、AVD Managerで設定するとできると教えてもらったので見てみたら確かに設定があり、実際に高速化した!Thanx。


Window>AVD Manager


Android 4.0.3のエミュを選択して「Edit」


「Hardware」で「New」


「GPU emulation」を選択して「OK」


「Hardware」に「GPU emulation」が追加されるので「Value」を「yes」に変更して「Edit AVD」あとはこのエミュをデバッグするなり何なりして起動させる。


GPUが使われていると、それはもう明らかに羽が生えたように軽くて速い。

ただ、やっぱりCPUはあまり高速化されていないようで、動作させたアプリではほとんど高速化の恩恵は感じられなかった。アプリよりもOSのホームスクリーンを移動したりする方が恩恵を感じられた。

たとえGPUエミュレーションがなかったとしても、全体的には2.3.3のx86エミュの方が高速だと感じた。


ちなみに、2.2エミュと2.3.3のx86エミュで上と同様の設定をして動作させてみたが、ロックスクリーンで固まった(^^);

この機能は4.0.3エミュのスペシャル機能と考えて間違いなさそうだ。

動作環境はこんな感じ。


Good Luck!

2012年4月10日火曜日

呆れるほどの解像度。Google「アートプロジェクト」。

日経12.04.10朝 日本の美、ネットで鑑賞 グーグル 国立博物館など6館の567点 無料で
グーグルは9日、東京国立博物館など6館の美術品を、インターネット上で検索・閲覧できるようにしたサービス「アートプロジェクト」を発表した・・・最大70億画素の高解像度・・・各館の所蔵品から著名なものを選び、著作権をクリアしたものを閲覧可能にした・・・利用者の閲覧は無料。グーグルは美術館側からも、料金を徴収しない。美術品の閲覧サービスを通して、美術に興味のある人向けの広告を利用者に表示するなどして、広告収入の増加につなげる・・・

おやまぁ・・・呆れるほどの解像度だ。

例えば、この狩野秀頼の高雄観風図。

http://www.googleartproject.com/ja/collection/tokyo-national-museum/artwork/maple-viewers-kano-hideyori/453020/

最大ズームアウトだとこんな感じ。


最大ズームインだとこんな感じ。


ビューアーはHTML+Javascriptで構築されていると思われる。少なくともFlashではない。

操作感はGoogle Mapsによく似ている。Google Mapsの技術を積極的に美術に応用したものと思われ。


 世界の名画がデジタルデータになり、世界中から誰もがアクセスできる形で永遠に残ることになるわけだ。すばらしい。

AndroidのICS(4.0.3)エミュがバージョンアップ。GPUサポートで高速化?SDK Toolsもrev.18にアップデート。

A Faster Emulator with Better Hardware Support | Android Developers Blog

・・・The system image we’re shipping today has built-in GPU support (Android 4.0.3 r2)・・・it’s now possible to use a tethered Android device to supply inputs for sensors and multi-touch input・・・CPU operations to be emulated roughly twice as quickly・・・

ICS(4.0.3)のエミュレーターがバージョンアップしたらしい。


  • GPUサポートで高速化
  • マルチタッチ入力のサポート(マルチタッチデバイス実機を接続・テザリングしてのことだろうと。Magic Trackpadのことかな?)
  • CPU処理が約2倍高速化


SDK Managerを見ると、確かにアップデートがあるみたい。4.0.3のシステムイメージとSDK Toolsのリビジョンが上がっている。


Help>Check for updateもチェックしろというのでチェックしたら、色々バージョンアップしてた。


先日rev.17になったばかりなのに、早くもrev.18ということらしい。

アップデート完了してICSの新しいエミュを動作させてみたけど、あんまり速くなっていないような・・・?OSとかGPUとか色々な組み合わせがあるだろうから、たまたま対応できなかったのかも。


追記12.04.11:この記事のコメントでAVD Managerで設定すると高速化されると教わり、やってみたら高速化した!Thanx 匿名さん。

琴線探査: Android 4.0.3 rev.2のエミュでGPUエミュレーションを使って高速化するには?

----

ただ、ICSのエミュが高速になったとしても、現状でのICSシェア率を考えればこのエミュをメインに使うわけにはいかないのが残念だ。

しかし、先日リリースされたx86エミュで2.3.3の動作が速くなったので良かったけど。

琴線探査: Android SDK・ADT rev.17でエミュレーターの高速化を試してみた。確かに速い。しかし・・・

ICSがGingerbread並の普及率になるのは、一体いつのことだろうか。

2012年4月9日月曜日

AndroidのボタンをXMLのみでスタイリング可能なShapeDrawableで描画するには?

AndroidのボタンをFlexと同様にプログラムを書かずにXMLのみでスタイリングをする方法を模索してきた。

琴線探査: AndroidのボタンをSVGで描画するには?
琴線探査: 続・AndroidのボタンをSVGで描画するには?(ローテクだけど高速版)
琴線探査: AndroidのボタンをShapeDrawableで描画するには?

単純な図形しか描画できないものの、今回である程度満足のいくやり方が固まった。


ボタンのテンプレート(shapeとselector)を作る

まずはres/drawableフォルダを作る。ここに次の3つのファイルを作る。


styleablebutton_up.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="#555555" />

    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />

    <corners android:radius="2dp" />

    <stroke
        android:width="1dp"
        android:color="#FFFFFF" />

</shape>


sytleablebutton_down.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="#AAAAAA" />

    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />

    <corners android:radius="2dp" />

    <stroke
        android:width="1dp"
        android:color="#FFFFFF" />

</shape>


styleablebutton.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/styleablebutton_down" android:state_pressed="true"/>
    <item android:drawable="@drawable/styleablebutton_up"/>

</selector>


attrs.xmlを作る

次に、このようにvalues/attrs.xmlをつくる。

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="StyleableButton">
        <attr name="fillColorUp" format="string" />
        <attr name="fillColorDown" format="string" />
    </declare-styleable>

</resources>


styles.xmlを作る

次に、このようにvalues/styles.xmlを作る。

このファイルはmain.xmlで楽をするために作ることにした。

しかし、main.xmlでそれぞれのボタン定義で同じ内容を記述する場合は必須ではない。ただ面倒なので作っておいたほうがいい。CSSのクラス定義のようなものだと考えるといい。

<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="styleablebutton" parent="@android:style/Widget.Button">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_margin">5dp</item>
        <item name="android:padding">5dp</item>
        <item name="android:background">@drawable/styleablebutton</item>
        <item name="android:textColor">#FFFFFFFF</item>
        <item name="android:textSize">10dp</item>
        <item name="android:text">StyleableButton</item>
    </style>

</resources>


スタイリング用カスタムクラスを作る

次に、ex.sdrbパッケージにこのようなStyleableButton.javaクラスを作る。

package ex.sdrb;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.AttributeSet;
import android.widget.Button;


public class StyleableButton extends Button {


  protected Context context;
  protected AttributeSet attrs;


  public StyleableButton(Context context, AttributeSet attrs) {

    super(context, attrs);
    this.attrs = attrs;
    this.context = context;

    StateListDrawable sld = (StateListDrawable) getBackground();
    TypedArray styles = context.obtainStyledAttributes(attrs, R.styleable.StyleableButton);

    String colorStr;
    GradientDrawable gd;

    // idx=0はDOWN
    colorStr = styles.getString(R.styleable.StyleableButton_fillColorDown);
    if (colorStr != null) {
      sld.selectDrawable(0);
      gd = (GradientDrawable) sld.getCurrent();
      gd.setColor(Color.parseColor(colorStr));
    }

    // idx=1はUP
    colorStr = styles.getString(R.styleable.StyleableButton_fillColorUp);
    if (colorStr != null) {
      sld.selectDrawable(1);
      gd = (GradientDrawable) sld.getCurrent();
      gd.setColor(Color.parseColor(colorStr));
    }

  }

}

背景に設定されたStateListDrawable(styleablebutton.xmlのセレクタ)を取得し、そこからUPステートとDOWNステートのDrawableを取得してattrs.xmlで定義したスタイルパラメーター名の値を読み込んで設定していく。

ここではStateListDrawableから取得できるDrawableがGradientDrawableであることがポイント。ShapeDrawableじゃないのね。


main.xmlの編集

res/layout/main.xmlをこのように編集する。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sdrb="http://schemas.android.com/apk/res/ex.sdrb"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:padding="10dp" >

    <ex.sdrb.StyleableButton
        style="@style/styleablebutton"
        android:text="StyleableButton" />

    <ex.sdrb.StyleableButton
        style="@style/styleablebutton"
        sdrb:fillColorDown="#FF4444"
        sdrb:fillColorUp="#553333" />

    <ex.sdrb.StyleableButton
        style="@style/styleablebutton"
        sdrb:fillColorDown="#44FF44"
        sdrb:fillColorUp="#335533" />

    <ex.sdrb.StyleableButton
        style="@style/styleablebutton"
        sdrb:fillColorDown="#4444FF"
        sdrb:fillColorUp="#333355" />

</LinearLayout>

ポイントは

・「xmlns:sdrb」のネームスペースの設定
・「ex.sdrb.StyleableButton」タグでボタン定義
・「style="@style/styleablebutton"」
・「sdrb:fillColorDown」
・「sdrb:fillColorUp」


実行結果


Eclipseプロジェクトファイル:StyleableDrawableResourceButton.zip(160KB)


まとめ

今回はスタイルパラメーターの定義をfillColorのUP、DOWNしかしなかったが、ストロークの幅や色など、attr.xmlとそれに対応するStyleableButton.javaの作り方次第で様々なパラメータ操作をXMLだけで行うことができることがわかった。

簡単なシェイプしか描画できないけど、単に枠を描画したいだけなら9patchを使うよりはるかに柔軟で楽だ。

また、attr.xmlのパラメーター定義は何もスタイリングだけに限らないだろう。パラメーターをメソッドの引数として考えれば、様々な動作をXMLだけで定義することができると思う。

2012年4月7日土曜日

Androidのカメラで撮った写真をダダ漏れにするには?

ちょっと、Androidのカメラで撮った写真をダダ漏れにする必要があったので調べてみた(^^);

ここで「カメラで撮った写真をダダ漏れにする」とは、カメラで撮った写真をWiFi・3Gネットワークのどちらに接続しているかに関わらず、世間一般誰でもWEBでアクセスできる場所に即座にアップロードするということだ。


Dropbox

まず言わずと知れたDropboxが写真の自動アップロード対応していたようだ。知らなかった。

「カメラアップロード」という機能がそれだ。この機能をONにするだけでいい。特にフォルダの指定などは必要ない。

アップロードすると、Dropbox上の「カメラアップロード」というフォルダに保存される。

ただ、この「カメラアップロード」フォルダは基本的にプライベートなので今回の用途には適さない。


普通の用途ならこれでいいんだろうけどね。


Google+

Google+も写真の自動アップロードに対応していたようだ。知らなかった(^^);

「インスタントアップロード」という機能がそれだ。この機能をONにするだけでいい。特にフォルダの指定などは必要ない。

アップロードすると、Google+上の「写真」>「携帯電話の写真」に保存される。Picasa上では「Instant Upload」アルバムだ。

ただ、これもDropboxと同様、基本的にプライベート状態でアップされるので今回の用途には適さない。


G+にシェアする用途としてはこれでいいんだろうけど。


PhotoSync


できるだけ現在使っているクラウドストレージかPicasaにアップしてくれるものを・・・という観点からこのアプリを選んだ。

  1.  Picasaで適当に画像をアップしてアルバムを作っておく
  2. 作ったアルバムを操作>アルバムプロパティ>公開設定>ウェブ上で一般公開にしておく
  3. PhotoSyncをAndroidでダウンロードする
  4. 起動してメニュー>追加
  5. 「Google Photoアルバム」で先ほど追加したアルバムを選択
  6. 「ローカルフォルダ」で「DCIM」を選択

あとはカメラでバンバン撮影すればいい。これバッチリ。しかも無料。すばらしい。

人にアルバムのURLを教える時にはURLの後ろに「?noredirect=1」を付けて教えてあげた方がいいかも。何だかG+の方にリダイレクトされてウザいので(^^);


で、iCloudではどうなの?

ところで、こういった「写真の自動アップロード機能」という意味ではiOSのiCloudが先駆けだと思う。

そこで、このように写真をダダ漏れにできるかどうか見てみたが、どうやらできそうにない。

iOSでも何かのアプリがあればできるかな?

官邸ホームページのリニューアルが批判されるのは、結局のところ、国民が国や政府を信頼していないからだと思う

きっこ女史がガセネタを官邸ホームページ更新の件で流して大漁旗が揚がる大戦果(やまもといちろう) - BLOGOS(ブロゴス)

・・・通常のサイトがレン鯖借りて運用するのとは訳が違う・・・あんな5,000ページにも及ぶ巨大サイトと4,000近いpdfリンクの山の更新作業を、300万で請ける下請けがいたとしたら、それはもう中国だろ… 常識で考えろというお話でございます・・・むしろ、費用的に安すぎて、標的型メールでクラックされて書き換えられる可能性すら残る・・・

何だかわからないけど、官邸ホームページがリニューアルした話が相当盛り上がっているらしい。

やまもと氏の記事を見ると、どうやらネット世論に便乗してアクセスを稼ごうという人たちがいるらしいこともその一因なのかもしれない。

実際にサイトを見てみて、確かにくだらなかったり、これ必要あんのか?と思うようなページもあった。

しかし、この官房長官の記者会見を見ると、今回の目玉は『全ての府省の政策情報をワンストップで調べることができる「政策情報ポータルサイト」』だとわかった。

平成24年4月2日(月)午後 | 平成24年 | 官房長官記者会見 | 記者会見 | 首相官邸ホームページ

そこでチェックしてみた。

キーワードから探す | 国の政策(政策情報ポータル) | 首相官邸ホームページ -

そして思った。これは4500万円程度じゃとても収まらないぞと。

仕様決定、見積り、デザイン、実装、プロジェクト管理、運用環境の構築・・・そして、何と言っても国のサイトなわけだから、国内最強レベルのセキュリティの確保など、そこらのサイト構築では考えられない細かで高い要求があったはず。これらに加えてこの検索システム・・・

もし万が一自分に請け負える能力があったとしても、1億円積まれてもやりたくないレベル。一言で言えば('A`)マンドクセな仕事だと思う。

しかし、サイト構築に関わったことのない人たちがこれを高いというのは理解できる。

原発再稼働問題や消費税の問題など、色々と頭にくることがありすぎるというのも批判したくなる理由なのかもしれない。

結局のところ、国や政府に国民の信頼がないのが根本的な原因だと思う。

「二元外交批判」というより、鳩山由紀夫元首相が国際舞台の場でまた変なこと言わないかが心配なんだ

イラン・イスラム共和国訪問にあたって(鳩山由紀夫) - BLOGOS(ブロゴス)

・・・これからイラン・イスラム共和国へ出発します・・・何としても武力衝突を避け、平和的に問題を解決すべきと考えてみれば・・・イランの最高レベルに直接働きかける重要性は高い・・・少なくとも国際社会の声を届け、問題解決に向けた環境整備の一助となればと考えています・・・「二元外交批判」を恐れていては議員外交はできなくなり、政府しか外交ができないようであれば、日本の未来は暗澹たるものになるでしょう・・・

いや、まったくごもっともだと思う。

しかし、問題はその交渉役が鳩山由紀夫元首相であるということだ。

「最低でも県外」といって、もはや収拾不可能ではないか?と思うほど沖縄の基地問題を混乱させたことをお忘れか?

ましてや今回は国際的な問題だ。

「二元外交批判」というより、鳩山由紀夫元首相が国際舞台の場でまた変なこと言わないかが心配なんだ。

電気事業法によれば、契約が合意されなくても電気は供給しなければならないことになっているそうだ

東電は電力供給を打ち切れない(河野太郎) - BLOGOS(ブロゴス)

・・・つまり、契約が折り合わなかったから電気の供給を止めますということにはならない。もし、需要家が希望すれば、東京電力は、届け出ている最終保障約款に基づいて電気を供給しなければならない。・・・契約が合意できなければ、最終保障約款に基づく電力供給に切り替えますといわねばならないところを、供給を打ち切りますなどと一方的に暴言を吐き続ける東京電力を経産大臣は許すのか。・・・

これは重要だなぁ。電気事業法によれば、契約が合意されなくても電気は供給しなければならないことになっているそうだ。

電気はライフラインだから、最悪の場合も考慮されているのだろう。法律って、結構しっかりしてるねと感心した。

しかし、こんなのぜんぜん知らなかった。ということは、東電は自社に有利な契約を結ばせるために、意図的に隠していたことになるのでは?

きっと「意図的ではありません。配慮が足りませんでした。」というのだろうけど。

2012年4月6日金曜日

AndroidのボタンをShapeDrawableで描画するには?

ベクター描画のオルタナティブ「Shape Drawable」

SVGでボタンを描画するのは、パフォーマンス的に問題があることがわかった。

琴線探査: AndroidのボタンをSVGで描画するには?

琴線探査: 続・AndroidのボタンをSVGで描画するには?(ローテクだけど高速版)

AndroidにはSVGと似たような方法でベクターのイメージを描画する方法がある。「Shape Drawable」だ。res/drawableフォルダにShapeを定義したXMLを配置すると参照、描画できる。

Drawable Resources | Android Developers

この方法がどれだけ速いか試してみよう。


ボタンとなるXMLを作成

まず、ボタンとなるShape DrawableのXMLを作成する。

res/drawableフォルダを作成する。その下に、次の2つのXMLを配置する。

roundbutton_up.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="#444444" />

    <padding
        android:bottom="8dp"
        android:left="8dp"
        android:right="8dp"
        android:top="8dp" />

    <corners android:radius="4dp" />

    <stroke
        android:width="2dp"
        android:color="#FFFFFF" />

</shape>


roundbutton_down.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="#999999" />

    <padding
        android:bottom="8dp"
        android:left="8dp"
        android:right="8dp"
        android:top="8dp" />

    <corners android:radius="4dp" />

    <stroke
        android:width="2dp"
        android:color="#FFFFFF" />

</shape>


ShapeDrawableResourceButtonクラス作成

ex.sdrbパッケージにShapeDrawableResourceButtonクラスを作成する。

ShapeDrawableResourceButton.java

package ex.sdrb;

import java.util.Date;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;


public class ShapeDrawableResourceButton extends TextView {


  protected Drawable dUp;
  protected Drawable dDown;


  public ShapeDrawableResourceButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    setClickable(true); // これをしないとタッチイベントがDOWNのみになる
    init();
  }


  protected void init() {

    Log.v("init", "スタイリング開始");
    long start = new Date().getTime();

    // リソース取得
    Resources res = getResources();

    // UP用Drawable取得
    dUp = (Drawable) res.getDrawable(R.drawable.roundbutton_up);

    // DONW用Drawable取得
    dDown = (Drawable) res.getDrawable(R.drawable.roundbutton_down);

    // UP用Drawableを背景にセット
    setBackgroundDrawable(dUp);

    long end = new Date().getTime();
    Log.v("init", "スタイリング終了 " + (end - start) + " msec");

  }


  @Override
  public boolean onTouchEvent(MotionEvent event) {

    // タッチイベントによって表示するDrawableを変える
    String action = "";
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        action = "ACTION_DOWN";
        setBackgroundDrawable(dDown);
        break;
      case MotionEvent.ACTION_UP:
        action = "ACTION_UP";
        setBackgroundDrawable(dUp);
        break;
    }
    Log.v("onTouchEvent", "action = " + action);

    // 再描画リクエスト
    invalidate();

    return super.onTouchEvent(event);

  }


}

SVGButtonのプログラムと比べると大幅に単純になっている。

XML読み込みの部分をResourcesクラスがやってくれるのと、画面密度によるスケーリングを自分で処理しなくて良いということが大きい。


レイアウトを編集

res/layout/main.xmlをこのように編集する。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:padding="10dp" >

    <ex.sdrb.ShapeDrawableResourceButton
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:text="ShapeDrawableResourceButton"
        android:textColor="#FFFFFF" />

</LinearLayout>


実行結果


ボタンをクリックするとハイライトする。

同様の処理をSVGButtonで行った時は4000msec程度、SVGButtonNoDOMで行った時は1100msec程度、そして今回は何と100msec程度!SVGButtonNoDOMと比べても11倍も速い。圧倒的だ。

Eclipseプロジェクトファイル:ShapeDrawableResourceButton.zip (157KB)


問題点

まず問題なのは、このShapeDrawableを使う方法では複雑なグラフィックを表現できないことだ。基本的に四角、楕円、線、リングしか描けない(^^);

もう1つの問題は、Resourceから取得したDrawableの色などをプログラムで変更できないこと。DrawableのAPIドキュメントを見る限り、残念ながらできなさそう。

ただ、このスピードは捨てがたいなぁ。

お?AIRでiOSアプリのUSBデバッグができるようになったかも?

Flash Player 11.3 と Adobe AIR 3.3 ベータ版の公開 - akihiro kamijo

・・・
iOS アプリの USB デバッグ
・・・
ADT コマンドから iOS シミュレータ用にアプリをパッケージ可能。実際のデバイスが無くても、iOS アプリの動作確認ができる
・・・
Mac App ストアの新要件への対応
・・・

お?AIRでiOSアプリのUSBデバッグができるようになった?お〜。

続・AndroidのボタンをSVGで描画するには?(ローテクだけど高速版)

簡単に高速化する方法は?

昨日、AndroidのボタンをSVGで描画するには?という記事を書いた。

琴線探査: AndroidのボタンをSVGで描画するには?

結論として、パフォーマンスの問題が残った。

DOM4Jを使えば少し速くなるけど十分ではなかった。

そこで、自ら高速だと宣言する「SJXP」というXMLパーサーを試してみたが、今回の用途には合わないようだった。(Attributeパース時のイベントがどうもうまく取れない)

根本的解決のためにはsvg-androidライブラリの改造が必要だと思う。しかし、もっと簡単に速くするワークアラウンドはないものか・・・(^^);

そこで非常にローテクだけど超高速化する方法を思いついた゜∀゜!!

要するに、XMLの処理が重いなら、XMLの処理をしなければいいんだ。

つまり、SVGのXMLをDOMではなくテキストとして取得し、String.replaceAll()を使って変更したい色の文字列を変更しちゃえばいい!


コードを書き換える

今回、SVGの中で変更したい部分はここ。

<path id="inner" fill="#4D4D4D" d="M8.334,35C4.29,35,1,31.298,1,26.747V9.253C1,4.703,4.29,1,8.334,1h91.332 C103.71,1,107,4.703,107,9.253v17.494c0,4.551-3.29,8.253-7.334,8.253H8.334z"/>

そこで、SVGButton.javaの一部分をこのように書き換えた。

protected void init() {

    // SVGのXMLのロードと画面密度の取得
    // (svgDocがstaticであることに注意。つまり、SVGButtonがいくつも作られる場合は最初の一度だけ行うことになる)
    if (svgDoc == null) {
      try {
        svgDoc = loadResourceAsString(R.raw.roundbutton); // SVGのテキストをロード
        screenDensity = getDisplayMetrics(context).density; // 画面密度を取得
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    // SVGが読み込まれてたら
    if (svgDoc != null) {
      try {

        //Log.v("init", "スタイリング開始");
        long start = new Date().getTime();

        // スタイルパラメーターをパース
        TypedArray styles = context.obtainStyledAttributes(attrs, R.styleable.SVGButton);

        //UP用にSVGドキュメントを変更
        String docUp = replaceStyleValue(svgDoc, styles, R.styleable.SVGButton_innerColorUp, "#4D4D4D", "#444444");
        
        // UP用Picture取得
        picUp = SVGParser.getSVGFromString(docUp).getPicture();

        //DOWN用にSVGドキュメントを変更
        String docDown = replaceStyleValue(svgDoc, styles, R.styleable.SVGButton_innerColorDown, "#4D4D4D", "#999999");
        
        // DOWN用Picture取得
        picDown = SVGParser.getSVGFromString(docDown).getPicture();

        // UP用Pictureを背景描画に設定
        picOnScreen = picUp;

        long end = new Date().getTime();
        Log.v("init", "スタイリング終了 " + (end - start) + " msec");

      } catch (Exception e) {
        e.printStackTrace();
      }
    }

  }


  public final String loadResourceAsString(int resourceid) throws IOException {
    long start = new Date().getTime();
    //Log.v("loadResourceAsString", "開始");
    StringBuilder sb = new StringBuilder();
    InputStream is = getResources().openRawResource(resourceid);
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr, 1024*8); //1024*8を指定しないとwarnが出る
    String line = null;
    while ((line = br.readLine()) != null) {
      sb.append(line);
    }
    br.close();
    isr.close();
    is.close();
    long end = new Date().getTime();
    Log.v("loadResourceAsString", "終了 " + (end - start) + " msec");
    return sb.toString();
  }


  protected String replaceStyleValue(String doc, TypedArray styles, int styleId, String replaceValue, String defaultValue) {
    long start = new Date().getTime();
    //Log.v("replaceStyleValue", "開始");
    String val = styles.getString(styleId);
    if (val == null) {
      val = defaultValue;
    }
    String tmpDoc = doc.replaceAll(replaceValue, val);
    long end = new Date().getTime();
    Log.v("replaceStyleValue", "終了 " + (end - start) + " msec");
    return tmpDoc;
  }

ポイントは

  • loadResourceAsString()でSVGをテキストとして取得
  • replaceStyleValue()を使ってSVGの指定した部分の値を変更

といったところだろうか。

上で書き換えた部分以外は、すべてオリジナルと同じ。

Eclipseプロジェクトファイル:SVGButtonNoDOM.zip (274KB)


結果と注意点

こうすることで、標準APIで4000msec程度かかる処理が1100msec程度で処理できるようになった。約3.6倍高速だ!

しかしこの方法だと、SVGのソースレベルで気をつけておかないと変えたくないところまで変わってしまうなど、様々な問題が起こる可能性がある。

とは言え、ある程度気をつけておけばかなり速い。

これなら実用に耐えるのではないだろうか。