Android Live Wallpaper

2013-09-10T00:00:00+00:00 Android Java

いわゆるライブ壁紙っていう機能。SurfaceView的な感じで壁紙に動的にレンダリングしたりとか出来る

まぁ自宅のメモ用ブログには書いてたけどこっちに書いてなかったので、美人時計サービスを利用して1分毎に動的に壁紙をレンダリングするっていうのをやってみる。ただ、結構雑に作ってあるので(ry

※あくまでLive Wallpaperってどんな感じで作るのかっていう

仕様

Live Wallpaperの仕様じゃなくて美人時計の仕様として http://www.bijint.com/jp/tokei_images/%02d%02d.jpg というような方式でリクエストすれば画像が取れるらしい。これ検証したのが結構前な話になるのだけど、現状でもそれは変わってはいないが昔はリファラーな辺りをチェックされていて美人時計内部からのリクエストだと模倣させないといけなかったような気がするけど現在そういう仕様は存在しない模様

んまぁちょっと余談がありましたが、結局は一分おきに上記のURLにリクエスト飛ばして画像取ってレンダリングするだけ

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="1"
    android:versionName="1.0" package="net.kinjouj.app.bijin_clock_wallpaper">

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18"/>
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:allowBackup="false">

        <service
            android:name=".BijinClockLiveWallpaperService"
            android:permission="android.permission.BIND_WALLPAPER">
            <intent-filter>
                <action android:name="android.service.wallpaper.WallpaperService" />
            </intent-filter>
            <meta-data
                android:name="android.service.wallpaper"
                android:resource="@xml/wallpaper" />
        </service>

        <activity
            android:name=".BijinClockLiveWallpaperPreferenceActivity"
            android:exported="true" />

    </application>
</manifest>

Live Wallpaperなレンダリングを行うのはサービスなのでそれを定義する。ActivityはPreferenceActivityなやつで今回は使わないので省略

res/xml/wallpaper.xml

<?xml version="1.0" encoding="utf-8" ?>
<wallpaper
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:thumbnail="@drawable/downloading"
    android:settingsActivity="net.kinjouj.app.bijin_clock_wallpaper.BijinClockLiveWallpaperPreferenceActivity" />

android:thumbnailでライブ壁紙一覧で表示される部分に画像を出せる模様。あくまでプレビューとは違うのかも

android:settingsActivityを設定しておく事でライブ壁紙を設置する際に設定を行う事が出来る。その設定は上記で省略したPreferenceActivityを用いる

んまぁあとはWallpaperServiceな実体なサービスクラスを作る

BijinClockLiveWallpaperService.java

package net.kinjouj.app.bijin_clock_wallpaper;

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.service.wallpaper.WallpaperService;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
import android.util.Log;

public class BijinClockLiveWallpaperService extends WallpaperService {

    private static final String TAG = BijinClockLiveWallpaperService.class.getName();

    @Override
    public Engine onCreateEngine() {
        return new BijinClockLiveWallpaperEngine();
    }

    private class BijinClockLiveWallpaperEngine extends Engine {

        private static final String BASE_URL = "http://www.bijint.com/jp/tokei_images/%02d%02d.jpg";

        private boolean enabled = true;
        private boolean fetched = false;
        private Handler handler = new Handler();
        private Bitmap currentBitmap;
        private Bitmap nextBitmap;
        private Date currentDate;
        private Date nextDate;

        private final Thread mFetch = new Thread("fetch") {
            @Override
            public void run() {
                Looper.prepare();

                try {
                    if(currentBitmap == null) currentBitmap = getImage(null);

                    while(enabled) {
                        if(isVisible()) handler.post(mDraw);

                        nextDate = calculateDate();

                        new Thread("fecth_inner") {
                            @Override
                            public void run() {
                                nextBitmap = getImage(nextDate);
                            }
                        }.start();

                        long ellapse = nextDate.getTime() - currentDate.getTime();
                        Thread.sleep(ellapse);

                        if(nextBitmap != null) {
                            currentDate = nextDate;
                            currentBitmap = nextBitmap;
                        }

                        nextBitmap = null;
                    }
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }

                Looper.loop();
            }
        };

        private Thread mDraw = new Thread("draw") {
            @Override
            public void run() {
                drawCanvas();
            }
        };

        @Override
        public void onCreate(SurfaceHolder holder) {
            super.onCreate(holder);
            Log.v(TAG, "onCreate");
            handler.post(mDraw);

            if(!isPreview() && !mFetch.isAlive()) mFetch.start();
        }

        @Override
        public void onOffsetsChanged(float xOffset, float yOffset, float xStep,
            float yStep, int xPixels, int yPixels) {
            super.onOffsetsChanged(xOffset, yOffset, xStep, yStep, xPixels, yPixels);

            handler.post(mDraw);
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);
            handler.post(mDraw);
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);

            try {
                if(mFetch.isAlive()) {
                    Looper.myLooper().quit();
                    mFetch.stop();
                }
            } finally {
                enabled = false;
                handler.removeCallbacks(mDraw);

                if(!isPreview()) Process.killProcess(Process.myPid());
            }
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            super.onVisibilityChanged(visible);

            if(visible)
                handler.post(mDraw);
            else
                handler.removeCallbacks(mDraw);
        }

        private void drawCanvas() {
            if(!isVisible()) return;

            SurfaceHolder holder = getSurfaceHolder();
            Canvas c = null;

            try {
                c = holder.lockCanvas();

                if(c != null) {
                    int width = holder.getSurfaceFrame().width();
                    int height = holder.getSurfaceFrame().height();

                    if(!isPreview()) {
                        if(currentBitmap == null) return;

                        c.drawBitmap(
                            Bitmap.createScaledBitmap(currentBitmap, width, height, true),
                            0,
                            0,
                            null
                        );
                    } else {
                        Bitmap bmp = BitmapFactory.decodeResource(
                            getResources(),
                            R.drawable.downloading
                        );
                        c.drawBitmap(
                            Bitmap.createScaledBitmap(bmp, width, height, true),
                            0,
                            0,
                            null
                        );
                    }
                }
            } finally {
                if(c != null) holder.unlockCanvasAndPost(c);

                handler.removeCallbacks(mDraw);
            }
        }

        public Date calculateDate() {
            if(currentDate == null) {
                currentDate = new Date();

                return currentDate;
            }

            Date now = new Date();
            now.setSeconds(0);

            if((now.getTime() - currentDate.getTime()) > 60000) currentDate = now;

            Calendar cal = Calendar.getInstance();
            cal.setTime(currentDate);
            cal.add(Calendar.MINUTE, 1);

            Date date = cal.getTime();
            date.setSeconds(0);

            return date;
        }

        @SuppressLint("DefaultLocale")
        public Bitmap getImage(Date date) {
            if(fetched)
                return null;
            else
                fetched = true;

            if(date == null) date = new Date();

            Bitmap bmp = null;

            try {
                String url = String.format(BASE_URL, date.getHours(), date.getMinutes());
                HttpClient httpClient = new DefaultHttpClient();
                HttpGet request = new HttpGet(url);
                byte[] b = httpClient.execute(
                    request,
                    new ResponseHandler<byte[]>() {
                        @Override
                        public byte[] handleResponse(HttpResponse response)
                            throws ClientProtocolException, IOException {

                            byte[] b = null;

                            if (response.getStatusLine().getStatusCode() == 200) {
                                BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());
                                b = EntityUtils.toByteArray(entity);
                            }

                            return b;
                        }
                    }
                );

                if (b != null) bmp = BitmapFactory.decodeByteArray(b, 0, b.length);
            } catch(IOException e) {
                Log.e(TAG, "ERROR", e);

                bmp = null;
            } finally {
                fetched = false;
            }

            return bmp;
        }
    }
}

ロックの仕方がまったくダメパターンだけど... (ほぼ数年前に書いたコードをそのまま)

んまぁ一定時間(1分)おきくらいにURLから画像を取得してCanvasにBitmapをレンダリングするだけ。

んまぁあとは自分で検証してください

testem+mocha+coverjsでコードカバレッジ angular.jsのkarmaを使ったテスト