AndroidQtの環境作成

以前一度試した時には、まだきちんとリリースされてなかったので実行までたどり着けなかった。
2月中旬に発表されて、手をつけないまま6月になってしまってるんだけれど、、
TechBoosterの記事にでも、と手をつけたらハマってしまったので覚書き。
※未解決です。


まず、本家のLink
necessitas

続いて、Ministro
Ministro
※Ministroは、Android上でQtアプリを実行するためのライブラリのインストーラー(だよね?)
※Qtアプリの初回起動時に、 Qtほげ.soファイルを取得してくれる。

手順

手順としては、本家のLink先から
「Download it」→「necessitas-0.2-online-sdk-installer-linux
をダウンロードし実行。

実行した際のスクリーンショットを順次貼っていく。

Install先のフォルダの設定

SDK/NDK共に持っていたのでINSTALLはせず。
どちらも必須(特にNDK)っぽいので、持ってない場合はここで入れること。

INSTALLの開始。
結構な時間がかかるので、覚悟しておくこと。

INSTALL完了。
チェックボックスにチェックを入れておけば、Creatorが起動する。

Creatorの起動。
二回目以降は、

/QtCreator/bin/necessitas

を実行する。

TOOL -> Optionにて、SDK/NDKのPATH設定をする。

あとは、サンプルプロジェクトを走らせて起動確認すればいいだけ。
と思いきや。。。

問題点

■ ant debug でこける。
Androidのbuildバージョンにも寄るが、ant1.8以降が求められている様子。
※手動Buildで確認するとエラーメッセージ詳細が得られます。
Build.xmlのあるところで、 ant debug してみましょう。

しかし、Ubuntu環境でのStatableなantは1.7.4(2011/06/18)
ココからバイナリを拾ってきました。

無事にBuildが通ることを確認。
※後で気づいたのですが、どうやらAndroidQtの動作確認環境はAndroid2.2r2みたい。

■ 実行直後に落ちる
antの問題が解決すれば、実機/Emulator問わず、アプリケーションの実行のところまではこぎつけれる。
しかし、起動直後に落ちる。

エラー内容は、

I/Qt JAVA ( 6232): java.lang.UnsatisfiedLinkError: Cannot load library: link_image[1962]:    75 could not load needed library 'libQtDeclarative.so' for 'libsample3.so' (load_library[1104]: Library 'libQtDeclarative.so' not found)

というもの。

と、ここでログを出そうとサンプルを起動してると、
2つ作ってたサンプルの片方が動くようになってるし。なんだこれww
どこか設定に依存してるのかもしれませんね。要調査。

Android3.0と3.1とRenderScriptと。

Android3.0 HoneyCombには、新しく(実は昔からあるそうな?)RenderScriptという3D描画用のエンジンが載っていたりするのですが、、、

先日のAndroid3.1の発表と共に、RenderScriptを使ったアプリケーションが動かなくなってしまいました。
動作的には、アプリ起動直後にエラー終了。

動作ログはというと、

E/AndroidRuntime(24361): FATAL EXCEPTION: main
E/AndroidRuntime(24361): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android.samples/com.android.samples.RsList}: android.renderscript.RSRuntimeException: Loading of ScriptC script failed.
E/AndroidRuntime(24361): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1736)
E/AndroidRuntime(24361): 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1752)
E/AndroidRuntime(24361): 	at android.app.ActivityThread.access$1500(ActivityThread.java:123)
E/AndroidRuntime(24361): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:993)
E/AndroidRuntime(24361): 	at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(24361): 	at android.os.Looper.loop(Looper.java:126)
E/AndroidRuntime(24361): 	at android.app.ActivityThread.main(ActivityThread.java:3997)
E/AndroidRuntime(24361): 	at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(24361): 	at java.lang.reflect.Method.invoke(Method.java:491)
E/AndroidRuntime(24361): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
E/AndroidRuntime(24361): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
E/AndroidRuntime(24361): 	at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(24361): Caused by: android.renderscript.RSRuntimeException: Loading of ScriptC script failed.
E/AndroidRuntime(24361): 	at android.renderscript.ScriptC.(ScriptC.java:60)
E/AndroidRuntime(24361): 	at com.android.samples.ScriptC_rslist.(ScriptC_rslist.java:32)
E/AndroidRuntime(24361): 	at com.android.samples.RsListRS.initRS(RsListRS.java:119)
E/AndroidRuntime(24361): 	at com.android.samples.RsListRS.init(RsListRS.java:82)
E/AndroidRuntime(24361): 	at com.android.samples.RsListView.ensureRenderScript(RsListView.java:39)
E/AndroidRuntime(24361): 	at com.android.samples.RsListView.(RsListView.java:28)
E/AndroidRuntime(24361): 	at com.android.samples.RsList.onCreate(RsList.java:32)
E/AndroidRuntime(24361): 	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1048)
E/AndroidRuntime(24361): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1700)
E/AndroidRuntime(24361): 	... 11 more

こんな感じ。
initializeの部分で死んでるとか言われても、手の打ちようが無いわけで。

現象を纏めると。
 ・元々、Android3.1のSDKが来るまで動いていた
 ・3.1のSDKが来てからは、手元の3.0XOOMでは動かないAPKも、3.1XOOMでは動いてた。
 ・RenderScriptのサンプル全て同じ現象
 ・自分で作ってたのも同じ用に動かない


で、原因は、、
Android3.1の発表と共に配られた「platform-tools revision4」の問題。
これをrevision3に落とすと、この問題は解決しました。


以下を残したいがために記事を書いたんだけど。

現在SDKのダウンロードは(platform-tools含む)

/tools/android

を使用して最新を落としてくる。

で、下位バージョンを落とすためには、
参考リンク:Android SDK ライブラリ強制インストール

以下にブラウザからアクセスすればいい。(platform-toolsに限る)
http://dl-ssl.google.com/android/repository/platform-tools_r◯-△.zip

◯ → revision
今回は 「03」を指定。
△ → OSの種類
今回は 「macosx」と記載。

これで無事に、手持ちの3.0XOOMでもRenderScriptで遊べるようになりましたとさっ!

C2DM方式を考えてみる

C2DM(プッシュ)方式について。(サーバサイド寄り)
ブログには直接書いてませんが、GoodMoorningTweet(以下、GMT)というアプリを出してます。

簡単に説明すると、twitterで誰かが自分に対し
ping
と打てば設定した音が鳴る
というアプリです。

で、この現在はアプリ、Twitterサーバに対し一定間隔でTLの取得
を行ってるわけですが…
例えば1分間隔で毎回TL取得しに行くと電池の消費量がどうしても増えちゃうわけです。

そこで今回、ポーリング方式からC2DM(プッシュ)方式
に変えてみようと考えてます。(まだ作ってません!)

最初はGoogleののC2DMサーバを使用する方法を検討していたわけですが
Sign in - Google Accounts


googleのアカウント情報が必須
・Android2.2以上必須
・定期的に登録IDがリフレッシュされる
Android Marketが必要

という制限があったのでthrow Lifeさんの記事
http://www.adamrocker.com/blog/311/ore-ore-c2dm.html
に載っているオレオレC2DMで行くことに。


現状の動きはこんな感じです。


改造後はこんな感じ。


C2DM方式の場合、pingを確認する処理がサーバの処理となり、GMT
ただpingの通知を待つだけになります。

GMTサーバからGMTへデータを投げる方法はthrow Lifeさんの記事の通り
Channel API
を使います。

このChannel APIはサーバから「Webページ」へプッシュするためのAPIです。
なのでGMTで一定時間ごとにTLを確認していたサービスに
Webページを持たせておきます。
これで、C2DM方式が実現でき(るはず!)ます。

とりあえずGAEがどこまで無料で使えるのか、どの程度のユーザまで耐えれるのか
などの調査も兼ねて、作ってみたいと思います。

「UDGりくえすと」はじめました。【雑記】

本日は、少しいつもとは志向を変えたお話。

「UDGりくえすと」の配信を始めたきっかけとかについて書いておく。

きっかけ自体はすごい単純で、ふとしたことでお目にかかったアプリに
「すげー。」
って思ったこと。


すげー。でも全然知らなかった。こんなに面白いのに。

マーケットの宣伝って出来ないのかな?

動いてる所見せてくれるのが、一番宣伝になるよね?

なんか出来ないかな。


という、ごにょごにょしたのを、わいわい話し合ってたら出てきたのが

それなら、自分たちで紹介番組つくっちゃえばいいじゃん!

 →「UDGりくえすと」

でした。


きっと、Marketにはすごい良い物が実は埋もれてたりすると思うんだ。

下手な放送で、上手く説明出来ないかもしれないけれど、

紹介に一役買わせてください。

そんなきっかけになればいいなって思ってます。


■□■□■□■□■□■□■□
日時:毎週日曜日 20時45分〜21時ぐらいまで
URL: http://p.tl/d-oo
内容:AndroidApplicationを @kobashinG と @seit がUSTで実況紹介します。
   随時Twitterで紹介アプリ募集中。
   開発者様も、ユーザー様も、どしどしりくえすとをお待ちしてます。

※録画も置いておりますので、いつでもご覧になってくださいっ

さくらVPSのUbuntuにRedmineをほげほげ。。

久しぶりに書くわけだけど、お久しぶりです。
ちょっと前から、チケットドリブンな開発に興味を惹かれていて、
身内で回してるプロジェクトに導入できないかと考えてた。

そんなこんなで、さくらVPSUbuntu-10.04 amd64Redmineを入れた時のメモ。

参考にしたサイト
http://risin.jp/archives/119

ほとんどリンク先の通りで大丈夫なんだけど、
空っぽのUbuntuからじゃ詰まる所があったので勝手補足メモ。

◯readlineのインストールには gcc/g++ が必要。

$ sudo apt-get install gcc g++

◯reeのインストールには build-essential libreadline5-dev libssl-dev zlib1g-dev が必要。

$ sudo apt-get install build-essential libreadline5-dev libssl-dev zlib1g-dev

◯passenger-install-apache2-module の実行に足りないのは
 ・libcurl4-openssl-dev(リンク先にも記載)
 ・apache2-mpm-prefork
 ・apache2-prefork-dev
 ・libapr1-dev
 ・libaprutil1-dev

$ sudo apt-get install libcurl4-openssl-dev apache2-mpm-prefork apache2-prefork-dev libapr1-dev libaprutil1-dev

SVNのインストール

$ sudo apt-get install subversion

。。。mysqlがインストールされてる前提だと?
お勉強します。ちょっと寝かすです。

設定→端末情報→電池使用量がいかがなものか調べてみた

先日のAndroid関西支部「情報交換会」に参加してきました。
LTにも参加したんだけど、私は下っ端扱いだったので触れません。
きっとseitさんが書いてくれるんじゃないかな?w

さてさて、勉強会の中で、電源管理のお話がでました。
タスク切れば電源が持つってのは違うんだよ?
アプリいっぱい眠ってると電池が減る訳じゃないんだよ?
ってお話があって、

kobashinG「あれ?設定→端末情報→電池使用量ってアテにできないのかな?」

と疑問が湧いたので調べてみました。

設定アプリは

 $ANDROID-HEAD/package/apps/Settings

にありました。

まずはAndroid端末で問題の画面に行って、
画面上の表示を確認。

それをgrep

$ cd ./package/apps/Settings
$ find . -type f -print | xargs grep --color -n "電池使用量" /dev/null

結果からぽいのを探す。

./res/values-ja/strings.xml:864:    "電池使用量"

次はAndroidManifest.xmlからこれを使ってるActivityを探す。
面倒なのでgrepした。

./AndroidManifest.xml:766:                android:label="@string/power_usage_summary_title"

ほしい結果が得られたので、見に行く。

$ vim AndroidManifest.xml
765         <activity android:name=".fuelgauge.PowerUsageSummary"
766                 android:label="@string/power_usage_summary_title"
767                 android:clearTaskOnLaunch="true"
768                 >
769             <intent-filter>
770                 <action android:name="android.intent.action.MAIN" />
771                 <action android:name="android.intent.action.POWER_USAGE_SUMMARY" />
772                 <category android:name="android.intent.category.DEFAULT" />
773                 <category android:name="com.android.settings.SHORTCUT" />
774             </intent-filter>
775         </activity>

どうやら、PowerUsageSummaryってActivityの様子。
早速追っかける。

onCreate()から怪しいとこを読みあさって、
見つけたのがこの処理。

    private void processAppUsage() {
        SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        final int which = mStatsType;
        final int speedSteps = mPowerProfile.getNumSpeedSteps();
        final double[] powerCpuNormal = new double[speedSteps];
        final long[] cpuSpeedStepTimes = new long[speedSteps];
        for (int p = 0; p < speedSteps; p++) {
            powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
        }
        final double averageCostPerByte = getAverageDataCost();
        long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which);
        mStatsPeriod = uSecTime;
        updateStatsPeriod(uSecTime);
        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
        final int NU = uidStats.size();
        for (int iu = 0; iu < NU; iu++) {
            Uid u = uidStats.valueAt(iu);
            double power = 0;
            double highestDrain = 0;
            String packageWithHighestDrain = null;
            //mUsageList.add(new AppUsage(u.getUid(), new double[] {power}));
            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
            long cpuTime = 0;
            long cpuFgTime = 0;
            long gpsTime = 0;
            if (processStats.size() > 0) {
                // Process CPU time
                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
                        : processStats.entrySet()) {
                    if (DEBUG) Log.i(TAG, "Process name = " + ent.getKey());
                    Uid.Proc ps = ent.getValue();
                    final long userTime = ps.getUserTime(which);
                    final long systemTime = ps.getSystemTime(which);
                    final long foregroundTime = ps.getForegroundTime(which);
                    cpuFgTime += foregroundTime * 10; // convert to millis
                    final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
                    int totalTimeAtSpeeds = 0;
                    // Get the total first
                    for (int step = 0; step < speedSteps; step++) {
                        cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
                        totalTimeAtSpeeds += cpuSpeedStepTimes[step];
                    }
                    if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
                    // Then compute the ratio of time spent at each speed
                    double processPower = 0;
                    for (int step = 0; step < speedSteps; step++) {
                        double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
                        processPower += ratio * tmpCpuTime * powerCpuNormal[step];
                    }
                    cpuTime += tmpCpuTime;
                    power += processPower;
                    if (highestDrain < processPower) {
                        highestDrain = processPower;
                        packageWithHighestDrain = ent.getKey();
                    }

                }
                if (DEBUG) Log.i(TAG, "Max drain of " + highestDrain 
                        + " by " + packageWithHighestDrain);
            }
            if (cpuFgTime > cpuTime) {
                if (DEBUG && cpuFgTime > cpuTime + 10000) {
                    Log.i(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
                }
                cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
            }
            power /= 1000;

            // Add cost of data traffic
            power += (u.getTcpBytesReceived(mStatsType) + u.getTcpBytesSent(mStatsType))
                    * averageCostPerByte;

            // Process Sensor usage
            Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
            for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
                    : sensorStats.entrySet()) {
                Uid.Sensor sensor = sensorEntry.getValue();
                int sensorType = sensor.getHandle();
                BatteryStats.Timer timer = sensor.getSensorTime();
                long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000;
                double multiplier = 0;
                switch (sensorType) {
                    case Uid.Sensor.GPS:
                        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
                        gpsTime = sensorTime;
                        break;
                    default:
                        android.hardware.Sensor sensorData =
                                sensorManager.getDefaultSensor(sensorType);
                        if (sensorData != null) {
                            multiplier = sensorData.getPower();
                            if (DEBUG) {
                                Log.i(TAG, "Got sensor " + sensorData.getName() + " with power = "
                                        + multiplier);
                            }
                        }
                }
                power += (multiplier * sensorTime) / 1000;
            }

            // Add the app to the list if it is consuming power
            if (power != 0) {
                BatterySipper app = new BatterySipper(packageWithHighestDrain, DrainType.APP, 0, u,
                        new double[] {power});
                app.cpuTime = cpuTime;
                app.gpsTime = gpsTime;
                app.cpuFgTime = cpuFgTime;
                mUsageList.add(app);
            }
            if (power > mMaxPower) mMaxPower = power;
            mTotalPower += power;
            if (DEBUG) Log.i(TAG, "Added power = " + power);
        }
    }

ちょっと長ったらしいんだけど、上から順に
・CPU使用時間
TCPのrecv/sendの量
・sensor使用時間
を計算してる。
計上されていくのは「power」って子。
そいつを最後にBatterySipperってのに入れてリスト化。

そいつはどこにいくのかっていうと、

    private void refreshStats() {
        ....省略
        processAppUsage();
        processMiscUsage();

        mAppListGroup.setOrderingAsAdded(false);

        Collections.sort(mUsageList);
        for (BatterySipper sipper : mUsageList) {
            if (sipper.getSortValue() < MIN_POWER_THRESHOLD) continue;
            final double percentOfTotal =  ((sipper.getSortValue() / mTotalPower) * 100);
            if (percentOfTotal < 1) continue;
            PowerGaugePreference pref = new PowerGaugePreference(this, 
    ....省略

        if (DEBUG) setTitle("Battery total uAh = " + ((mTotalPower * 1000) / 3600));

sipperっていうのが、BatterySipperのインスタンスで、
sipper.getSortValue()で突っ込んだ「power」が取得できる。

DEBUGで切られてるけど、mTotalPowerが総Ahとすると、

            final double percentOfTotal =  ((sipper.getSortValue() / mTotalPower) * 100);

ここでパーセント計算してるかな。

とすると、
結局「電池使用量」さんは

・CPU使用時間
TCPのrecv/sendの量
・sensor使用時間

をしっかり見てることになる。
ここを目安に、アプリのお行儀を調べるのもありじゃないかなー?

ブラウザの「ページを共有」からURLを取り出してみる

Tech Boosterに投稿するつもりだったが、あえなくボツとなってしまったネタ。このままお蔵入りおもったいないので、ここに残す。

Androidでは、暗黙的Intentを用いて、アプリ間でのデータの受け渡しを行うことが可能です。
ここではその一例として、ブラウザのページの共有機能を用いて、ウェブページのURLを取得する方法をご紹介します。

早速、まずはIntentの送信側を見てみましょう。

//Ex.)1
public static final void sharePage(Context c, String title, String url,
       Bitmap favicon, Bitmap screenshot) {
   Intent send = new Intent(Intent.ACTION_SEND);・・・①
   send.setType("text/plain");
   send.putExtra(Intent.EXTRA_TEXT, url);・・・②
   send.putExtra(Intent.EXTRA_SUBJECT, title);
   send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon);
   send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot);
   try {
      c.startActivity(Intent.createChooser(send, c.getString(
      R.string.choosertitle_sharevia)));
   } catch(android.content.ActivityNotFoundException ex) {
      // if no app handles it, do nothing
   }
}

こちらは標準ブラウザのソースコードの一部で、メニューから「ページを共有」を選択した際に呼び出される処理です。
②でIntentにURLを格納しているのがわかります。
今回、受信側では、ここで格納されているURLを取り出します。

では、受信側の処理を見てみましょう。

まず、「ページを共有」から起動できるようにするために、AndroidManifest.xmlを以下の記述を追加します。

//Ex.)2


     
     
     
  

これで、ページを共有の一覧に自分のアプリのアイコンが表示されます。
暗黙的Intent(送信側アプリ)では、受信側アプリのタグ内のcategory、mimeType、actionの3つを、システム側で判断させて、起動するアプリを決めています。
今回は、送信側(Ex.)1)で、actionACTION_SENDを、mimeTypetext/plainをそれぞれ設定していますので、受信側でも同様に、actionにACTION_SEND、mimeTypeにtext/plainを設定しています。

ちなみに、暗黙的Intentを用いて画像のギャラリーアプリケーションを呼び出したい場合は、actionACTION_PICKmimeTypeimage/*を設定することで呼びだすことができました。

では、受信したintentからURLを取り出します。その前にex.)3を見てみましょう。

//Ex.)3
 
    
        
          
          
        

    


        
           
           
           
        

    

ここでは、Activityが2つ定義されていますが、アプリケーションを通常起動した場合は「Sample01Activity」に、ページの共有からアプリケーションを起動した場合は「Sample02Activity」が、それぞれ起動するようになっています。
によって、どのタイミングで起動させたいかを定義しているのです。

さて、今回はページの共有からの暗黙的Intentによる呼び出しなので、Sample02Activityが起動します。このActivity内でIntentからURLを取り出し、取り出したURLをTextViewで表示してみます。
以下がその一部です。

//Ex.)4
 if(Intent.ACTION_SEND.equals(getIntent().getAction())){
    //URLを取り出す
    CharSequence uri = getIntent().getExtras().getCharSequence(Intent.EXTRA_TEXT); ・・・①

    if(uri != null){
      TextView textView = new TextView(this);
      textView.setText((String)uri);・・・②

      layout.addView(textView);
    }
 }

①でEXTRA_TEXTをキーにURLを取得しています。ここで取得したURLはオブジェクト型なので、今回はTextViewで文字列として表示するため、②でString型でキャストしています。


今回は例として、「ブラウザアプリ」からページを共有機能を用い、「自作アプリケーション」でURLを受け取ってみました。このように、暗黙的Intentを用いれば、同アプリ内でのActivity間だけでなく、アプリケーション間でのオブジェクトの受け渡しを行うことが可能です。