masaibarの雑記

胃腸は弱いが肉は好き

Play Billing LibraryのCodeLabをなぞってみた

はじめに

開発しているアプリに課金要素を入れたいと思い、そのために新しいPlayBillingLibraryを覚えたかったのでチュートリアルとしてCodeLabをなぞってみた覚書。

ちなみにCodeLabだけ全部やっても本番投入するには不充分であるというのが結論。

Buy and Subscribe: Monetize your app on Google Play

目次

手順

セットアップ

まずはチェックアウトしてくる。

$ git clone https://github.com/googlecodelabs/play-billing-codelab

このままAndroidStudioで開くのではなくworkのフォルダを開くことに注意。

開くと、サンプルコードが一部古いようなのでupdateしてやる。 f:id:masaibar-dev:20171112123704p:plain

ベースとなるアプリの確認

完成図としてはこんな感じになるらしい。 f:id:masaibar-dev:20171112124754p:plain

とりあえず何も考えずビルドしてみるとこんな画面が出てくる。 f:id:masaibar-dev:20171112125103p:plain

画面に表示されているのは下記の要素。

  • 車と燃料の残量を表示
  • 燃料を消費して運転ゲームをプレイできるボタン
  • 空の購入画面(これから実装していく)を開くための購入ボタン

ここから下記のようにアプリ機能を拡張していく。

  • Googleデベロッパーコンソールに定義した商品詳細を表示
  • GooglePlay料金確認ダイアログ経由での購入

Play Billing Libraryとの統合

build.gradleファイルにBillingライブラリを追加

app/build.gradleにBillingライブラリの定義を追加してSyncする。

compile 'com.android.billingclient:billing:1.0'

BillingClientの作成

既存のBillingManagerのコードを編集していく。 PurchasesUpdatedListenerをimplementsするのを忘れないように。

public class BillingManager implements PurchasesUpdatedListener {

    private static final String TAG = "BillingManager";

    private final BillingClient mBillingClient;
    private final Activity mActivity;

    public BillingManager(Activity activity) {
        mActivity = activity;
        mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
        mBillingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(int responseCode) {
                if (responseCode == BillingClient.BillingResponse.OK) {
                    Log.i(TAG, "onBillingSetupFinished() response: " + responseCode);
                } else {
                    Log.i(TAG, "onBillingSetupFinished() error code: " + responseCode);
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                Log.w(TAG, "onBillingServiceDisconnected()");
            }
        });
    }

    public void startPurchaseFlow(String skuId, String billingType) {
        // TODO: Implement launch billing flow here
    }

    @Override
    public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases) {
        Log.d(TAG, "onPurchaseUpdated() response: " + responseCode);
    }
}

テスト

書き換えが完了した状態で、Runボタンを押すとGamePlayActivityのonCreateの中でBillingManagerの初期化が行われ、logcatに以下のログが出力される。

onBillingSetupFinished() response: 0

SKU詳細の取得

SKUとは在庫管理を行う場合の単位。アイテムは商品の種類を指すが、SKUは同じ商品でもパッケージの違いや値段の違いなど、アイテムより小さい単位で分類される。http://www.e-logit.com/words/sku.php

クエリの開始

一度onBillingSetupFinishedが呼ばれると、BillingClinetが使用できるようになる。

まず、SKU詳細を問い合わせてみる。

CodeLabのアプリには既に2つのアプリ内アイテム

  • gas
  • premium

と、2つの定期購読アイテム

  • gold_monthly
  • gold_yearly

が用意されている*1ので、それらの詳細を問い合わせるためにクエリを実行する。

まずはGooglePlayデベロッパーコンソールから特定SKUタイプの全てのSKU IDリストを取得するためのデータ構造を定義する。

static {
    SKUS = new HashMap<>();
    SKUS.put(BillingClient.SkuType.INAPP, Arrays.asList("gas", "premium"));
    SKUS.put(BillingClient.SkuType.SUBS, Arrays.asList("gold_monthly", "gold_yearly"));
}

public List<String> getSkus(@BillingClient.SkuType String type) {
    return SKUS.get(type);
}

次に、BillingManagerに新しいメソッドを追加し、GooglePlayデベロッパーコンソール上で定義された商品(SKU)に関する全ての詳細情報を取得出来るようにする。 このメソッドは情報を取得するための、SKUタイプとSKUのリストであるSkuDetailsParamsを受け取る。

    public void querySkuDetailsAsync(@BillingClient.SkuType final String type,
                                     final List<String> stringList,
                                     final SkuDetailsResponseListener listener) {

        SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
                        .setSkusList(stringList)
                        .setType(type)
                        .build();

        mBillingClient.querySkuDetailsAsync(skuDetailsParams, new SkuDetailsResponseListener() {
            @Override
            public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
                listener.onSkuDetailsResponse(responseCode, skuDetailsList);
            }
        });
    }

次にデータをUI上に表示する。

AcquireFragmentにhandleManagerAndReadyメソッドが予め定義されており、フラグメントを表示しBillingManagerがアクセス可能になった際に一度だけ呼ばれる。その為、アプリ内アイテムのSKU詳細を取得するためにこのメソッドをqueryで拡張する。

private void handleManagerAndUiReady() {
    // Start querying for SKUs
    List<String> inAppSkus = mBillingProvider.getBillingManager()
            .getSkus(BillingClient.SkuType.INAPP);
    mBillingProvider.getBillingManager().querySkuDetailsAsync(BillingClient.SkuType.INAPP,
            inAppSkus,
            new SkuDetailsResponseListener() {
                @Override
                public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
                    if (responseCode == BillingClient.BillingResponse.OK &&
                            skuDetailsList != null) {
                        for (SkuDetails details : skuDetailsList) {
                            Log.w(TAG, "Got a SKU: " + details);
                        }
                    }
                }
            });

    displayAnErrorIfNeeded();
}

テスト

アプリを起動し、Purchaseボタンを一度押すとAcquireFragmentが表示され、handleManagerAndUiReady()が呼び出される。 logcatには以下のログが出力される。

Got a SKU: SkuDetails: {"productId":"gas","type":"inapp","price":"¥113","price_amount_micros":113484176,"price_currency_code":"JPY","title":"Gas (Play Billing Codelab)","description":"Buy gasoline to ride!"}
Got a SKU: SkuDetails: {"productId":"premium","type":"inapp","price":"¥170","price_amount_micros":170226264,"price_currency_code":"JPY","title":"Upgrade your car (Play Billing Codelab)","description":"Buy a premium outfit for your car!"}

SKU情報の描画

アプリ内アイテムの表示

このサンプルでは、描画のためのUIが予め用意されているためクエリの結果をadapterに繋ぐだけで良い。 最初に、AcquireFragment#handleManagerAndUiReady()でローカルのSkuRowDataの配列に入れ直すため、onSkuDetailsResponseを次のようにする。

if (responseCode == BillingClient.BillingResponse.OK &&
        skuDetailsList != null) {
    List<SkuRowData> inList = new ArrayList<>();
    for (SkuDetails details : skuDetailsList) {
        inList.add(new SkuRowData(
                details.getSku(), details.getTitle(), details.getPrice(),
                details.getDescription(), details.getType()));
    }
    if (inList.size() < 1) {
        displayAnErrorIfNeeded();
    } else {
        mAdapter.updateData(inList);
        setWaitScreen(false);
    }
}

apkをビルドして実機に入れ、Purchaseボタン押下後に表示されるAcquireFragmentが次のように表示されていることを確認する。

f:id:masaibar-dev:20171112204959p:plain

ちなみにCodeLabのサンプル画像だと値段が円表記ではなく$表記になっている。 恐らく端末の言語設定によって表示が違うのではないかと思われる。

f:id:masaibar-dev:20171112205102p:plain

定期購読アイテムの表示

CodeLabでGoogleデベロッパーコンソール上に用意されているのは定期購読アイテムもあるので、それらも同様に表示できるようにする。

このタスクはアプリ内アイテム追加とよく似ているため、コードのコピペを避けるためにAcquireFragment.handleManagerAndUiReady()内のgetSkuDetailsメソッドで利用する、再利用可能なリスナーを定義する。

final List<SkuRowData> inList = new ArrayList<>();
SkuDetailsResponseListener responseListener = new SkuDetailsResponseListener() {
    @Override
    public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
        if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
            for (SkuDetails details : skuDetailsList) {
                Log.i(TAG, "Found sku: " + details);
                inList.add(new SkuRowData(details.getSku(), details.getTitle(),
                        details.getPrice(), details.getDescription(),
                        details.getType()));
            }

            if (inList.size() == 0) {
                displayAnErrorIfNeeded();
            } else {
                mAdapter.updateData(inList);
                setWaitScreen(false);
            }
        }
    }
};

この再利用可能なリスナーと拡張可能なリストを準備したら、アダプター内にアプリ内アイテムと定期購入アイテムを簡単に入れることが出来る。 どちらのタイプのSKUに対しても、同じSkuDetailsResponseListenerでクエリを投げることが出来る。

// Start querying for in-app SKUs
List<String> skus = mBillingProvider.getBillingManager().getSkus(SkuType.INAPP);
mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.INAPP, skus, responseListener);
// Start querying for subscriptions SKUs
skus = mBillingProvider.getBillingManager().getSkus(SkuType.SUBS);
mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.SUBS, skus, responseListener);

これらの変更後、handleManagerAndUiReadyは次のようになる。

private void handleManagerAndUiReady() {
    final List<SkuRowData> inList = new ArrayList<>();
    SkuDetailsResponseListener responseListener = new SkuDetailsResponseListener() {
        @Override
        public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
            if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
                for (SkuDetails details : skuDetailsList) {
                    Log.i(TAG, "Found sku: " + details);
                    inList.add(new SkuRowData(details.getSku(), details.getTitle(),
                            details.getPrice(), details.getDescription(),
                            details.getType()));
                }

                if (inList.size() == 0) {
                    displayAnErrorIfNeeded();
                } else {
                    mAdapter.updateData(inList);
                    setWaitScreen(false);
                }
            }
        }
    };

    //// Start querying for in-app SKUs
    List<String> skus = mBillingProvider.getBillingManager().getSkus(BillingClient.SkuType.INAPP);
    mBillingProvider.getBillingManager().querySkuDetailsAsync(BillingClient.SkuType.INAPP, skus, responseListener);

    // Start querying for subscriptions SKUs
    skus = mBillingProvider.getBillingManager().getSkus(BillingClient.SkuType.SUBS);
    mBillingProvider.getBillingManager().querySkuDetailsAsync(BillingClient.SkuType.SUBS, skus, responseListener);
}

テスト

ビルドしてPurchaseボタンを押すと次のように表示されるはずである。 f:id:masaibar-dev:20171112211905p:plain

購入フローの開始

GooglePlayの料金設定ダイアログを表示

下記のコードをBillingManager#startPurchaseFlow()に実装すると、AcquireFragmentのボタンを押された時に呼び出される。 また商品のSKUと、ボタンを押された商品の請求タイプも知っている。

※デモンストレーションなのでクレジットカードを追加しないこと。

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
        .setType(billingType).setSku(skuId).build();
mBillingClient.launchBillingFlow(mActivity, billingFlowParams);

テスト

デバイスにインストールして、1番上のGasのカードのBUYボタンを押すと次のような画面が表示される。 f:id:masaibar-dev:20171112212153p:plain

最後の仕上げ

基本的な再試行のポリシーを実装

例えばPlayStoreアプリがバックグラウンドでアップデート中だった場合など、何らかの理由でPlayStoreサービスが切断された場合は再接続しようとするのが道理である。

切断後にBillingClient#startConnection()を一度だけ実行するような基本的なリトライ機構をBillingManagerに実装する。

これを実現するために、BillingClient#isReady()メソッドを利用する。

そして、BillingClinetの接続開始処理を再利用可能にするために、BillingManagerのコンストラクタから別メソッドに移動する。

また、クライアントが接続されていなかった場合か、接続成功時に一度だけ呼ばれるようなRunnableな引数を追加する。

private void startServiceConnectionIfNeeded(final Runnable executeOnSuccess) {
   if (mBillingClient.isReady()) {
       if (executeOnSuccess != null) {
           executeOnSuccess.run();
       }
   } else {
       mBillingClient.startConnection(new BillingClientStateListener() {
           @Override
           public void onBillingSetupFinished(@BillingResponse int billingResponse) {
               if (billingResponse == BillingResponse.OK) {
                   Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
                   if (executeOnSuccess != null) {
                       executeOnSuccess.run();
                   }
               } else {
                   Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
               }
           }
           @Override
           public void onBillingServiceDisconnected() {
               Log.w(TAG, "onBillingServiceDisconnected()");
           }
       });
   }
}

何らかの理由でクライアントが切断された場合、このメソッドが再接続を一度試みる。 例えば、上記再試行ポリシーを使用してSKUの詳細クエリを実装するなら次のようにする。

public void querySkuDetailsAsync(@BillingClient.SkuType final String itemType,
            final List<String> skuList, final SkuDetailsResponseListener listener) {
        // Specify a runnable to start when connection to Billing client is established
        Runnable executeOnConnectedService = new Runnable() {
            @Override
            public void run() {
                SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
                        .setSkusList(skuList).setType(itemType).build();
                mBillingClient.querySkuDetailsAsync(skuDetailsParams,
                        new SkuDetailsResponseListener() {
                            @Override
                            public void onSkuDetailsResponse(int responseCode,
                                    List<SkuDetails> skuDetailsList) {
                                listener.onSkuDetailsResponse(responseCode, skuDetailsList);
                            }
                        });
            }
        };

        // If Billing client was disconnected, we retry 1 time
        // and if success, execute the query
        startServiceConnectionIfNeeded(executeOnConnectedService);
}

startPurchaseFlow()メソッドはこのようになる。

public void startPurchaseFlow(final String skuId, final String billingType) {
        // Specify a runnable to start when connection to Billing client is established
        Runnable executeOnConnectedService = new Runnable() {
            @Override
            public void run() {
                BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                        .setType(billingType)
                        .setSku(skuId)
                        .build();
                mBillingClient.launchBillingFlow(mActivity, billingFlowParams);
            }
        };

        // If Billing client was disconnected, we retry 1 time
        // and if success, execute the query
        startServiceConnectionIfNeeded(executeOnConnectedService);
}

リソースの掃除

Javaではリソースを綺麗にし、メモリリークを避けることが大切なので、この部分も抜かりなく。

全てのリソースを消去し、Observerを登録解除するのはBillingClient#endConnection()を呼び出すだけである。 BillingManagerの内部に下記メソッドを実装し、GamePlayActivity#onDestroy()から呼び出す。

public void destroy() {
   mBillingClient.endConnection();
}

終わりに

一連の実装を体験することによって、大体の概念と流れは把握できたように思う。

しかし、試しにGasを買ってみたはずが燃料に反映されていなかったり、再度GasのBUYボタンを押しても反応しない状態になっている。

複数購入できるようなアイテムであればローカルで消費したことにして、再度購入が出来るようにするべきだと思うし、買い切りのアイテムであれば購入された時点でそれを検知してボタンをdisableにするなどCodeLabの内容だけでは実用に耐えうるクオリティではなさそうだ。

Play Billing Library | Android Developers

公式のドキュメントを読んで更に理解を深める必要があるが、予め必要な部品などは用意されており入門としてやる分にはCodeLabは良い物だと思う。

*1:通常、GooglePlayデベロッパーコンソールからSKUを設定する。 変更が反映されるまでに数時間かかるため、ここでは予め用意されたSKUを利用する。

Huawei社製端末の「保護されたアプリ」設定画面をこじ開ける

はじめに

近頃値段の割に高性能とうことでHuawei製のAndroid端末が多く出回っております。 私自身、HuaweiのGR5という端末を購入したのですが数々のメーカー独自の素敵設定があるお陰で購入当初は苦労しました。 そんな素敵設定の中の一つに「保護されたアプリ」という項目があります。 これは保護されていないアプリに関しては、画面ロック時に問答無用でプロセスを殺すというオチャメな機能です。

www.sim-jozu.net

大概のユーザーはこのような設定があることをつゆ知らず、LINEやGmailが届かなくなったと騒ぎ、私の個人開発しているアプリでも「いきなり動かなくなったぞ、早く改善しろ」という温かいメッセージをいただく度にggrkという言葉を飲み込んでこの機能の事を説明して設定を見直してもらっております。

流石にメーカー側も問い合わせが多かったのか、LINE等のメジャーどころに関しては最近の端末ではデフォルトで保護されているアプリに追加しているようですが、殆どのアプリはそこまで特別待遇をしてもらえるわけではありません。

そこで、今回ご紹介するのはこの設定画面を直接開くためのコードです。 これによってHuawei社製端末の場合のみ設定を案内することで、問い合わせ件数を減らせないかと思い現在組み込み実装中です。

実装

簡単に説明をするとhasProtectedAppSetting()で設定画面の有無を確認後、存在していた場合にのみopenProtectedSettings()を呼ぶことで設定画面を開いてくれます。 ※試験できる実機が手元のGR5しかないので全てのHuawei端末で正しく動く保証はありません。

public class HuaweiUtil {

    private static final String PACKAGE_NAME_HUAWEI_SYSTEM_MANAGER = "com.huawei.systemmanager";
    private static final String ACTIVITY_NAME_HUAWEI_PROTECT_ACTIVITY =
            ".optimize.process.ProtectActivity";

    private Context mContext;

    public HuaweiUtil(Context context) {
        mContext = context;
    }

    public boolean hasProtectedAppSettings() {
        Intent intent = new Intent();
        intent.setClassName(
                PACKAGE_NAME_HUAWEI_SYSTEM_MANAGER,
                String.format("%s%s",
                        PACKAGE_NAME_HUAWEI_SYSTEM_MANAGER, ACTIVITY_NAME_HUAWEI_PROTECT_ACTIVITY)
        );

        List<ResolveInfo> resolveInfos = mContext.getPackageManager()
                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);

        return resolveInfos.size() > 0;
    }

    public void openProtectedSettings() {
        try {
            String cmd = String.format(
                    "am start -n %s/%s",
                    PACKAGE_NAME_HUAWEI_SYSTEM_MANAGER,
                    ACTIVITY_NAME_HUAWEI_PROTECT_ACTIVITY
            );

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                cmd = String.format("%s --user %s", cmd, getUserSerial());
            }

            Runtime.getRuntime().exec(cmd);
        } catch (IOException ignored) {
        }
    }

    private String getUserSerial() {
        Object userManager = mContext.getSystemService("user");
        if (userManager == null) {
            return "";
        }

        try {
            Method myUserHandleMethod =
                    android.os.Process.class.getMethod("myUserHandle", (Class<?>[]) null);
            Object myUserHandle =
                    myUserHandleMethod.invoke(android.os.Process.class, (Object[]) null);
            Method getSerialNumberForUser =
                    userManager.getClass()
                            .getMethod("getSerialNumberForUser", myUserHandle.getClass());
            long userSerial = (Long) getSerialNumberForUser.invoke(userManager, myUserHandle);

            return String.valueOf(userSerial);

        } catch (NoSuchMethodException | IllegalArgumentException |
                InvocationTargetException | IllegalAccessException ignored) {
        }
        return "";
    }
}

おわりに

実際の設定値の取得方法まではわからない(多分外部アプリに対して公開はされていない)ため、設定済みのユーザーにも案内してしまう可能性が出てきてしまうものの、設定画面を直接開いて設定を促せることによって苦しむユーザーと問い合わせ件数を減らせるのではないでしょうか。

また、自分はあまりスマホに詳しくないのだというユーザーの皆様につきましては、不具合かなと思ったらとりあえず価格コムの該当端末に関する口コミを確認すると同じことで苦しんでいる人と解決方法が見つかるやもしれませんので一度ご確認をオススメしますし、調べずに問い合わせるにしても高圧的な態度で来られるとこちらも人間なので辛いです。

ちなみに買った端末はこちら。 購入当時スペックの割に安かったし指紋認証もあって値段相応の満足はさせてもらいました。

上述の「保護されたアプリ」に加え、ロック画面に通知が出なかった(有料アプリでカバー)り、未だに5.1からアップデートされる気配がないのでそろそろ買い替えたい所存ですが。

今もしHuawei端末を買うのならP10 Liteの評判が良さそうです、同僚も買ってました。

参考

stackoverflow.com

CAMPFIRE Growth Hack #2 に参加してきたのでまとめ

CAMPFIRE Growth Hackとは

ヤフーが「グロースハック」にフォーカスした情報共有を定期的に行う、勉強会/交流会イベントです。

「勉強会は数多くあれどグロースハックを主題にしたものはあまり無い、コミュニティを盛り上げていきたい」との一言で始まりました。

yj-meetup.connpass.com

発表

スポーツナビアプリ スモールチームでのアプリ強化の進め方

登壇者:今田 仁美さん(ワイズ・スポーツ株式会社)

  • スポーツナビアプリとは

    • Web版が元々あってそのアプリ版、リリースから2年弱で累計200万DL突破
    • コアバリューは「スポーツの「今」をどんなサイト・アプリよりも快適に、ストレスなく知ることができる」
  • 現場の課題

    • 人が少ない(常時4人ほどで回している)ので、大きな機能追加アップデートは難しい
    • ユーザー獲得のためのプロモーション施策の同時並行も難しい
    • 上から求められるアプリの成長は大きい
    • リリースから一定期間立つとユーザー数が伸び悩む
    • 「どうしたら少人数で効率よく高品質なアプリを提供し続けられるのか」が課題
    • 一旦立ち返って「チームのこと」を考えた
    • これまでのチームは一方通行気味のチーム。安全にリリースしたら終わり、で次のリリースへ
  • どんなチームになったか
    • 「ワクワクするチーム」とあるメンバー談
    • 週1回の定例を開き、重要なところは必ず全員で決め、全員が納得した状態で開発に入れるように。
    • 「案件ベースから長期的にアプリを考えられるチーム」
    • 納得感を得た状態でものづくりのフェーズに入ると一歩先が見えるようになり+αの提案が出来るように
    • 結果的にアプリの質の向上に繋がった
    • 最後に振り返りの場で褒め合う、労り合う
  • チームが同じゴールを目指すために「コアバリュー」を強く意識、ユーザーに何を届けたいか
    • 活発になった分議論がブレても方向修正
    • 纏める役割として、アイデアを肥大化させずに優先度付けして絞っていく(人数は増えなかった…)
  • 優先度の基準
    • スポーツアプリだと伸びるシーズンがある程度決まっている(五輪、シーズン開幕…etc)のでそこをゴールに定めた
    • DAUなど重要KPIに効くか
    • これを何のためにやっているのか
    • コアバリューに沿っているか
  • まとめ
    • アプリの前にチームを成長させる
    • その上で優先度決定をしっかり
    • 今田さん的には「褒め合う」ことが成長の秘訣なのではないかとのこと

発表資料

Android: スポーツナビ‐野球/サッカー/ゴルフなど速報、ニュースが満載 - Google Play の Android アプリ

iOS: スポーツナビを App Store で

サービスに新しい価値を作る方法

登壇者:外崎 匠さん(Retty株式会社 アプリプロジェクトリーダー)

  • すでにあるサービスに付加価値をつけるには?
    • すでにサービスのベースはある
    • サービスがある程度ユーザーに認められている
    • 今の世界観は壊せない
  • Rettyについての紹介 日本最大級の実名型グルメサービス
    • ある日CEOから「新しいお店探しの体験を作って欲しい」
    • まずはサービスの置かれた現状を把握しよう
    • 強み弱みを自分で考えて、自分事化することが大切
  • 強み
    • 「実名」であるがゆえに、口コミの信頼感があってポジティブなものが多い
  • 弱み
    • 他のユーザーがグルメ的にどんな人なのか分からないので、どの人のオススメがいいのか分からない
  • 戦略を立てよう
    • 現状の強みを活かして、弱みを解決する戦略を考える
    • 現状の世界観はなるべく壊したくない = 実名制を損ねず、出て来る人がグルメ的にどんな人かわかるように
    • グルメ度 × 専門性 = TOP USER
    • ある程度方向性が決まったら超高速でプロトタイピングを繰り返す
      • 考えた戦略が正しい方向か確認
      • 方向性が間違っていなかったら、よりユーザーに刺さるものを見つける
      • (間違っていたら止める勇気も大事
  • プロジェクトでのプロトタイピングでの事例
    • 大まかな方向性を確認
    • もう少しブラッシュアップする
    • 全体を俯瞰してみる
  • TOP USERの体験
    • ヘビーユーザーがなりたいと思えるインセンティブを設計
    • TOP USERでも取れないような会員制のお店を使ってのオフ会、Rettyへの愛着も形成
    • TOP USERの口コミを雑誌にして、グッズ化する
    • サービス外でTOP USER制度の認知やブランド力の向上
      • 投稿やプレスリリースを打つなど
      • TOP USERのメディア出演を積極的にサポート
  • まとめ
    • まずは現状把握(強み・弱み)
    • 強みと弱みを踏まえた戦略を立てる
    • 戦略を高速プロトタイピングで確認&ブラッシュアップ
    • 全体を俯瞰

発表資料

retty.me

FiNCアプリ流 チャットオンボーディングのグロースハック

登壇者:近藤 慶水さん(株式会社FiNC データ分析エンジニア)、百瀬 恵さん(株式会社FiNC アプリディレクター)

  • FiNCの紹介、3月頭にリリース ヘルスケアコンテンツ、歩数などのライフログ管理、チャットAiサポート
  • Retention Rate 継続率を重要指標にしている
  • 約3ヶ月改善の結果 1dayRR / 7dayRR /30dayRR 150~300%改善
  • RRの要因となるKPIとしてプッシュ通知許可率歩数データ取得率が存在
    • プッシュ通知 × 歩数データ = コア体験
    • 歩数データが記録されると、チャットAiが反応、プッシュ通知が届く 簡単、達成感、お得
    • プッシュ通知許可率が8,9割、歩数データ取得率も9割以上 プッシュ通知許可率に関しては驚異的な数値!
  • チャット形式のオンボーディング(≒チュートリアル)
    • プロフィール情報
    • 健康に関する悩みなどを聞く
    • プッシュ通知の許可などを求める
    • アカウント登録処理
    • 離脱を少なくするは当たり前だけど良い初期体験をしてもらうことが需要
    • 極端な話多少離脱率が上がっても、最終的ににKPI(継続率)が上がれば良い
    • なんの脈略もなくアプリを使い始めたときにpush通知許可 40%台(一般的なアプリと同数値)
    • オンボーディングに入れ、許可した際のメリットを提示 許可率70%台
    • (数十回のチューニングを経て)画像や文言やエフェクト等でわかりやすく伝える 許可率90%弱
  • 改善する現場で奮闘したこと
    • 「とりあえずbotでいいから、とスタートしたAiちゃん」=> 「なんでこいつ豆知識しか喋らないんだよ!」
    • 今後の構想を見越して汎用的なチャットマスタ作成を一ヶ月くらいの間行い、今のAiの基盤が出来上がる
    • いろんな会話を入れたいのに更新にひたすら時間がかかった => 今はエラー検知のためのテストが行えるようになりエラー文言も出るように、会話マスタ管理コンソール開発中
    • たったひとつの絵文字、されど絵文字 文言や絵文字・画像の使い方・Aiの表情等のディティールの改善を繰り返した
  • グロース出来る前提基盤とは
    • 絶対的な評価軸があった ユーザー継続率
    • 機能の役割が明確だった 連携率を上げる
    • リリース体制が整った 汎用設計/マスタ体制
    • 可視化環境が整った KPIダッシュボード

発表資料

finc.com

データで説明できないカスタマーの課題を解消するために。

登壇者:竹信 瑞基さん(株式会社リクルートジョブズ アプリプロダクトオーナー)

  • タウンワークについての紹介 バイトだけではなく正社員、派遣社員、業務委託など様々な雇用形態に対応
  • データで説明できない事象をどう解くか
    • カスタマーの声 データで説明できないことがある
    • スマホでのバイト検索=めんどくさい
    • 「おしゃれで楽そうなバイトってどう探すの?」
    • UU単位で生ログ見ても「検索条件が変わっていっている」傾向しか見えない…
    • 「なにが課題?」 わかっていることを、コトバにして整理
      • イメージを具体化出来ていない
      • スマホでの検索が面倒くさい
    • 構成要素を足すことで言語化出来た
    • 「前提」「課題仮設」「結果」に分解できた
  • 限られたスコープでどう打ち手を導くか
    • 現状理想状態に分けて整理
    • 前提状態から理想的な状態にするための打ち手は?
    • 働きたい仕事が見つかることが理想状態、そのための手段は問わない
  • オンボーディングで情報取得し求人レコメンド
    • しかし、求職者/求人者の利益を守るため、様々な法令を遵守という制約があり、意図的、恣意的に歪めるのは良くない
    • 何かしらの検索で目的を果たさねばならなくなった。
    • 能動的で複雑な検索が課題 => 受動的にシンプルな検索ならいいのでは?
    • 受動的な検索に寄り添うシンプルなUIをコンセプトにパターン出し
    • ジョブーブの相談部屋 質問に答えるだけで検索できる
    • しかし、質問について質問文言と検索条件との紐付けに制約条件は?
    • 法令を読み込み、法務・コンプラ部署に相談
    • 法令遵守を前提にレギュレーションを定義・合意形成
  • ROIが測れない企画をどう進めるか
    • 開発の優先順位≒ROIの大きい順 リターン最大化、コスト最小化
    • 主機能の検索に課題はありそう
    • リターンは検証でしかない、コストが問題ならコスト最小でやってみるしかない
    • 質問をランダム表示、位置を主要導線外で試す
      • 二週間の実装リリースでCVRが1.6倍以上
      • オンボーディングにペライチ追加、利用UUが3倍以上に
  • まとめ
    • 課題状態を構造化し、カスタマーの課題解決とプロダクト成長を図っていこう

townwork.net

前途多難でも関係ない!基本の型でハッピーグロースハッキング

登壇者:松下 三四郎さん(ヤフー株式会社 第6代 アプリグロースハック黒帯)

  • Yahoo! MAPについて 目的地が決まっていてナビゲーションをするアプリだった。
  • ミッションは「地域と人々の毎日をより便利に、豊かに」
  • リニューアルのタイミングで松下さんがjoin
  • レビュー大荒れ 日時平均2以下
    • 旧アプリとかなり違うコンセプトなのに、上書きでアップデート => *旧ユーザーにとって改悪アップデート:
    • 組織の課題
      • リニューアル後の進め方が決まっていない
      • ステークホルダーが多すぎる100人以上
      • 東京大阪名古屋の3拠点
  • 課題解決のために大切な3つのこと
    • コアバリュー お出かけに関するいい情報に出会えて探せて迷わず行ける
    • KPIツリー ビジネス観点ではなくサービス改善につながるものでないと意味がない
    • ロードマップ KGI, KPI, プロダクトの状態, ユーザー価値、メリット, やること ゴールを定める、そこに至るまで月ごとの戦略を定める
      • 新しい月に入ったら前月の振り返りを必ず行う
    • 全職種のキーマンを強引に巻き込んで決める
    • データからユーザの声を集める
  • 新ユーザと旧ユーザの2種類のユーザーがいる
  • 行動ログと声を集めて定量化する
  • 新ユーザーには初回体験を見直し、継続利用の仕組みを作る
    • コア機能の理解度を行動ログから定量的に分析
      • ある機能のあるステップまで踏んだユーザは翌日継続率が高いのでそれをより多くのユーザーにしてもらうために
  • ネガティブな意見を減らす
    • ネガティブとポジティブ両意見を引き出し定性的な意見を冷静に定量化する
    • 分析したユーザーの声/データをKPIツリーにプロット
    • 新規ユーザー 初回体験を見直し、継続利用の仕組みを作る
    • 旧ユーザー ネガティブな意見を減らし、新ユーザで成功した体験を旧ユーザーにも
  • まとめ
    • どんな状況でもゴールコアバリューを決める
    • ゴールを達成するためにロードマップを作る
    • 現状起きていることを把握し、戦略をアップデートしていく

発表資料

Yahoo! MAP - 【無料】ヤフーのナビ、地図アプリ - Google Play の Android アプリ

Yahoo! MAP - 地図、ナビ、お出かけ情報アプリを App Store で

まとめ

どの発表を聞いていても、まずは「コアバリュー」有りき。

当たり前だが、そのサービスは何を解決したいのかがブレているとユーザーにうまく届かないし、しかしそのようなサービスは少なくない。

次に現状を言語化、数値化し課題を解決するための指標を明確化させることが必要。

これも当たり前だが、何をどうすれば良いのかがぼんやりした状態では改善のアクションは起こせない。

後は明確化された指標を上げるためにそれぞれのサービスに合った手法があるといった感じだったが、特に今回の幾つかの発表で触れられていたオンボーディング(初回チュートリアル)を工夫して初回のユーザーをうまく導いてあげるのはとても効果が高そうだ。

余談ですがCAMPFIREでは懇親会のケータリングでお寿司が出ます、今回も美味しかったです。

f:id:masaibar-dev:20170717144138p:plain

朝起きたら玄関が開かなくなっていた件

今朝の出来事

朝から有明に行く用事があったため、前日は早めに就寝し5時半に起床した。

身支度も終わり、駅に向かうため玄関を開けようとしたところ何故かドアが開かない

我が家は賃貸だが玄関前に宅配ボックス1を置いており、何か荷物が届いていたのかなとも思ったがそもそも夜中に何かが届いているはずもない。

一体何が起こっているのか分からないが、扉を強く押してみると若干だが隙間ができた。

隙間を覗くとそこには



















おっさんの後頭部











おっさんが寝ていた

http://2.bp.blogspot.com/-l-r0zT7iUWg/Ufj1EmLDOWI/AAAAAAAAWlY/1G4qhRPHJdE/s400/gorogoro_ojisan.png

(画像はイメージです)











?!?!?!?!?!?!?!?!?!?!?!?!?!?!



理解不能な事態に心臓は早鐘を打っていたが、大声で「大丈夫ですか?」と呼びかけると朦朧とした感じで応答があった。

一瞬ホームがレスな方かとも思ったけど、身なり的にはそうも見えない。

昨晩から降り続いていた雨をしのぐため、酔っ払ったおっさんがうちの玄関前の屋根を頼りに寝てしまったのだろう。

人んちの玄関前で何やっているんだという怒りよりも電車を逃すと遅刻してしまうという焦りが勝ったため、とにかく扉の前から退いてもらい駅へ。

玄関を振り返るともう一度そこに横たわるおっさんの姿が見えたので、駅についてから最寄りの警察署へ通報。

後に報告の電話を貰ったがお巡りさんが見に来た頃にはおっさんの姿はなかったとのこと。

その後

無事に有明に着いたものの肝心の用事は雨のため流れてしまい、家に帰るとそこにはファミリーマートのレジ袋に入った未開封の金麦の缶が残されていた。

f:id:masaibar-dev:20170625220620j:plain

吐瀉物とかなくてホント良かった。

そのままにしておくわけにも行かず、かと言って飲む気にもなれず、朝のお礼も言いたかったので最寄りの交番に拾得物として届けることにした。

朝の方はおらず、また「財布や鍵ならともかく飲食物は原則拾得物として扱えないのだ」とのこと。

「気持ち悪いでしょうし、この場で処分しましょう」とご提案頂いたので近くのゴミ箱行きへ。

急いでたしおっさんの顔は全然覚えていないのだけど、こちらは自宅が割れているからちょっと不安だ。

万が一おっさんが家に「酒を返せ」と来たら警察立ち会いで処分した旨を伝えて、ゴネるようなら通報してくれと言ってもらえた。

交番にいる人は高圧的な態度の人が多い印象だったが、今日は親切なお巡りさんで良かった。

まとめ

お酒は節度を守って、楽しく飲みましょうね。

仮に酔っ払っても人の家の玄関前で寝るのはやめましょう。


  1. こんな感じの簡素なやつ。買ってからメチャクチャ捗ってるのでいつかちゃんと紹介したい。

    by カエレバ

アプリの評価を高くするために実践しているたった3つの事

はじめに

私は昨年末に個人でAndroidアプリをリリースしました。

のぞきみ ~既読回避をサポート~ - Google Play の Android アプリ

もうそろそろ出してから半年経つのですが、現時点で累計7万ダウンロードと初めての個人アプリとしては悪くない数字だと思っています。

また、評価も2017/06/19現在で平均4.6(合計460件)と客観的に見ても割と高い数字がついていると思います。 f:id:masaibar-dev:20170619233236p:plain

今回は、なるべく高い評価をつけてもらうために普段から実践している3つのポイントについてご説明していきます。

安心してください。「人を雇って大量に★5をつけます」みたいな手法じゃないです。

目次

悪くないものをリリースする

「良いものじゃないのかよ」と思われるかもしれませんが、ユーザーレビューは5点満点の減点方式です。

つまり、悪いものやバグを出すと一部のユーザーはこれ幸いと言わんばかりに噛み付いてきます。

消極的表現ではありますが「悪くないもの ≒ 最低限の期待値はクリアしているもの」のようなイメージです。

それではもう少し詳しく、最低限の期待値について掘り下げていきます。

クラッシュは少なく

まず、一番の前提条件としてはクラッシュが少ないことです。

ユーザーのそれまでの操作、場合によっては時間をかけた入力などが一瞬にして無に帰したときの怒りは計り知れません。

それが頻発するようであれば、良くてその場でアンインストール、悪ければレビューに罵詈雑言を書いてからアンインストールでしょう。

バグも少なく

バクも少ないに越したことはありませんが、特にアプリの根幹を揺るがすような致命的なバグは早めに潰したいところです。

致命的なバグは上記のクラッシュ同様ユーザーへ悪い印象と攻撃材料を与えてしまうため、レビュー等で指摘されたもので「これはヤバイ」というレベルのバグは一刻も早く修正するべきだと思います。

アップデート時にはいきなり100%公開するのではなく、段階的なリリースを選択することで様子を見ながら公開範囲を広げ、本当にヤバそうだったら途中で公開をストップすることも出来るのでオススメです。

しかし、軽微なバグ(判断は難しいですが)に関してはそこまで焦らなくてもいいのではないかなという認識です。 緊急度は高くないものの、優先度は高めにして次回のリリースのついで等で対応することによってユーザーに改善の意思があることを示すことが出来るのではないかと考えています。

自分の納得できるものを

仕事としての開発だと厳しいかもしれませんが、少なくとも気兼ねなくリリースを先延ばしに出来る個人アプリであるならば 自分の中でも納得の出来ていないような中途半端なものはリリースはするべきではないと考えています。

中途半端な状態でのリリースはバグの温床にもなりやすく、後から仕様を変える際にもユーザーからの反発を生む場合があります。

自分が納得できたものがユーザーに受け入れられない可能性ももちろんありますが、自分ひとり納得させられない機能に対しユーザーが魅力を感じることはないと考えます。

いい評価をしてくれそうな人を探す

個人的には、使っていると頻繁に評価を求めるダイアログが表示されるアプリは大嫌いです。

肝心なアプリの出来がイマイチなアプリなんかは、それだけで悪い評価をつけたくなる衝動に駆られることすらあります。

そのアプリに悪い印象を持っているユーザーに向けてそんなものを出すのは火に油を注ぐ行為に他なりません。 そのため私のアプリでは評価をお願いするユーザーをある程度選んでからお願いしています。

誰が高いレビューをつけてくれるのかということを考えると自ずといい印象を持っているユーザーだと分かります。

そのユーザーをどうやって見分けるのかの基準は難しいですが、少なくとも起動直後のユーザーや普段あまり使っていないユーザーは該当しないと考えます。

また、インストールしても満足できなかった場合にはすぐアンインストールすると考え、私のアプリでは起動回数やインストールからの経過日数等を判断材料にしてレビューへの導線を表示しています。

いい印象をもっているユーザーを更に絞る

ヘビーユーザーをある程度絞ったところで、更に良い印象を持ってくれているユーザーに絞り込むにはどうしたら良いのか。

単純な話です、ただユーザーに聞いてみればいいのです。

私のアプリでは、ダイアログを表示する頭からレビューをお願いするのではなく

「このアプリを気に入っていただけましたか? はい / いいえ / 今度答える」

というワンクッションを挟み、いい印象を持ってくれているユーザーを更に絞り込んでいます。 ここで「いいえ」を選択したユーザーに対しては別の導線を用意します(後述)。

上記の関門をすべて通り抜けたユーザーに対して初めて「良かったら応援をお願いします」という意図のダイアログを表示し、ストアの評価画面への誘導をしています。

また、これは余談ですが以前ネット上で少し話題になっていたファイアーエンブレムのスマホゲーが有りました。

あまりに露骨すぎるのもちょっとアレですがいい印象を抱いている人に褒めてもらうという観点ではこの手法自体はそこまで大きく間違っているとは思いません。 blog.esuteru.com

はけ口も用意しておく

前述のワンクッション部分で、「気に入っていない」と答えたユーザーに対しても真摯に向き合うべきだと考えます。

不満がありながらも数日間は使ってくれていたというのはありがたいことであり、その不満が何であるのかを知るために私のアプリではお問い合わせフォームへ遷移するような導線を作っています。

問い合わせフォームも別に凝ったものは作っておらず、GoogleフォームとGoogleスプレッドシートの合わせ技を使うことによってものの10分で投稿フォームと一覧管理画面が用意できます。

agn.jp

ときには心無い言葉が投稿されて気持ちが濁ることもありますが、ユーザーが何を期待しているのか、どういう機能が欲されているのかという生の声を一部分ではありますが知ることが出来るのは重要です。

もちろんユーザーの声に振り回されるのは本末転倒ですが、それでも数人から同じ要望が上がってくるようであれば次回以降のアップデート内容の一案として検討の余地があると思います。

バグや改善要望レビューは直したら返信する

ここまで悪い評価をつけそうなユーザーは極力排除しようとしても、アプリに対して強い不満を持つユーザーはこちらが誘導せずとも自分から悪いレビューを書きに来ます。

内容としては言いがかりに近いものもあれば、バグや仕様などこちら側に改善の余地があるものも存在しています。

言いがかりは華麗にスルーするとして、バグ修正や要望された機能を実装した後にはレビューへの返答という形でユーザーに一報入れるようにしています。

GooglePlayはレビューへ返信をするとそのレビューを書いたユーザーに対して通知が飛ぶので、改善を期待していたユーザーとしては「不満が解消された」、「自分のことを気にかけてくれている」という二点を感じることができ、低い確率ではありますが★1のレビューが★5に化けたりする場合があります、これはデカイです。

まとめ

至極当たり前の内容かもしれませんが私のアプリでは下記の3点を実践し、それなりの平均評価をもらっております。

  • 悪くないものをリリースする
  • いい評価をしてくれそうな人を探す
  • バグや改善要望レビューは直したら返信する

平均評価によってダウンロード数も大きく左右されるらしい1ので、ASO対策などと併せて実施していくと自然流入のユーザーが増えやすくなるのではないでしょうか。

また、ストアを見ていると日本国内ユーザーは残酷なまでに簡単に★1をつけていきますが海外ユーザーはその辺りが寛容で、評価平均は世界中から付いた合算で算出されるため海外でも公開しているようなアプリだと平均評価が高くなりやすいように見えます。だからちょっと怪しげで日本国内のレビューは寧ろボロボロあのアプリの評価4.7になってるのね

レビュー依頼ダイアログの出し方などはこの記事がとても参考になりました。 appmarketinglabo.net

PR

最後に宣伝にはなりますが、良かったら「のぞきみ」をよろしくお願いします。

play.google.com


  1. 私のアプリは最初から条件を全て満たすように作っており5.0から4.9、4.8とじわじわ落ちてきて安定してきたのが4.6なので今ひとつ実感できていない。「3.0から4.5に上がったらこの位伸びましたよ」ってのが示せないのは残念なのでどなたか実データをお持ちでしたら教えてください。

はてなブログproに独自ドメインを設定しようとして躓いた話

はじめに

ずっとブログを始めてみたいと思っていたがなかなか始めるきっかけを掴めず、先日新しくiMacを買ったのをきっかけに始めてみた。 また、どうせ始めるならある程度続けてみようと思い、身銭を切って自分にプレッシャーを掛ける意味で二日目にしてproにしてみた。 更に、どうせなら独自ドメインがカッコイイだろうと早速独自ドメイン契約して、いざ設定というところで罠にハマったという恥ずかしいお話。

時間のない方は

「はてなブログ開設時に設定したURLのドメインのことは忘れて、その上で手順書をよく読んでから作業するべし」

とだけ覚えて帰ってください。

help.hatenablog.com

ちなみに独自ドメインはお名前.comで契約しました。 www.onamae.com

何をしでかしたのか

お名前.comで設定するべきCNAMEのVALUEの箇所に間違った内容を入力してしまった。

その結果はてなのブログの設定画面で独自ドメインを入力し、待てど暮らせど一向に有効にならなかった。

f:id:masaibar-dev:20170614225050p:plain

ただ一点言い訳をさせてもらうと、はてなブログでは最初にブログを作る際にブログ名のあとのドメイン名を幾つかの中から選択することができる。

hoge.hatenablog.com
hoge.hatenablog.jp
hoge.hateblo.jp
hoge.hatenadiary.com
hoge.hatenadiary.jp

ここで私は最初hatenadiary.comを選択しており、当然ながらお名前.com側にもhatenadiary.comを入力するものだろうと思い込んでいた。

しかしながら、実際にははてなブログの[ヘルプ]にはきちんと明記されていたのだった。

f:id:masaibar-dev:20170615233938p:plain

こんなに重要なことなのに控えめすぎでは、もう少し強くアピールしてもらえませんかね

ときすでに遅し、www.masaibar.comはhatenadiary.comとして浸透してしまっていた。

[@~]$ nslookup www.masaibar.com
Server:     8.8.8.8
Address:    8.8.8.8#53

Non-authoritative answer:
www.masaibar.com    canonical name = hatenadiary.com.
Name:   hatenadiary.com
Address: 13.112.5.107

どうやってリカバリしたか

とりあえずお名前.com上とはてなブログの設定画面上から一度登録を削除した上で風呂に入った。

DNSの浸透にはどうしたって時間がかかる。

風呂から上がる頃には無事に登録が消えていた。

[@~]$ nslookup www.masaibar.com
Server:     8.8.8.8
Address:    8.8.8.8#53

** server can't find www.masaibar.com: NXDOMAIN

今度こそはとお名前.com側で再設定。

f:id:masaibar-dev:20170615000556p:plain

ちゃんとhatenablog.comになっていることを確認して設定を反映。

f:id:masaibar-dev:20170615000722p:plain

これで数分待つとDNSにも正しく登録された。

[@~]$ nslookup www.masaibar.com
Server:     8.8.8.8
Address:    8.8.8.8#53

Non-authoritative answer:
www.masaibar.com    canonical name = hatenablog.com.
Name:   hatenablog.com
Address: 13.112.5.107

はてなブログ側でも再登録、今度はちゃんと「有効」と登録された。 f:id:masaibar-dev:20170615001705p:plain

アドレスバーに打ち込んで遷移してもちゃんと表示される、めでたしめでたし。 f:id:masaibar-dev:20170615001829p:plain

まとめ

「独自ドメインの設定とかやったことないけど、仕事で二年間くらい運用業務やってたしまぁなんとかなるでしょ」みたいな驕りは正直あったと思う。

「知りもしないくせに手順書をよく読まなかった」ことが原因というなんとも恥ずかしい話である。

それとお名前.com側はもう少しだけユーザーに優しいUIになるともっと気持ちよく使える気がするので中の人は頑張って欲しい。

iMac 2017 Retina 5Kのメモリを64GBに増設した話

はじめに

初めてはてなブログを書きます。

前置きは特にいらんって人は、増設手順から読んで下さい。

ちなみに、メモリ差し替えの所要時間は確認しながら丁寧に作業しても5分くらいでした。

新卒で入社した年に買ったMac mini 2012Lateを母艦として使い続け、AndroidStudioとGoogleChromeによって動作がカクついてきていたためそろそろ買い替えたいと思っていたが、先日のWWDCでは心待ちにしていた新型Mac miniはついぞ発表されなかった。

しかし、iMacのマイナーアップデートが発表され5Kモデルには最大64GB1のメモリが積めるとのことだった。

デフォルトが8GBとなっており

  • 16GB +22,000円
  • 32GB +66,000円
  • 64GB +154,000円

とカスタマイズで購入するには16GB辺りが落とし所かなと思えるような価格設定であった。

しかし、27インチのiMacは自分でメモリを増設できるとの情報も入り、俄然購買意欲が高まってしまった。

別にPhotoShopや動画のエンコードをする予定はない私にとって64GBは明らかにオーバースペックではあるが、そこにはロマンがある。

とりあえずAppleStoreで27インチを注文した。

また、この時点でどのメモリを買えばいいか混乱状態になっていたのはまた別のお話2である。

増設手順

日本のサポートページには2017/06/13現在まだ情報が出ていないので、海外の公式ページを参考に作業を進めていく。

まずは増設前のメモリの確認、購入時はデフォルトの8GB(4GB×2枚)が認識されている。

f:id:masaibar-dev:20170613235559p:plain

コンセントの差込口の上部にあるボタンを押してメモリ格納部の蓋を空けていく。 が、ここのボタンが中々硬いので付属されていたキーボード充電用のLightningケーブルの先端でぐいっと押す。(破損の恐れがありますので真似しないで下さい)

f:id:masaibar-dev:20170613235331p:plain

フタを開けると内部にはこのように2枚のメモリが刺さっていた。スロットは全部で4つなので2つは空スロットだった。 左右にあるレバーの表示通り外側に拡げながら上にレバーを立てることが出来る。

f:id:masaibar-dev:20170613235914p:plain

レバーが立った状態になるとメモリの抜き差しが可能になる。

f:id:masaibar-dev:20170614000126p:plain

もともと刺さっていたメモリを抜き、購入したメモリ(16GB×4枚)を向きを間違えないようにして差し込んでいく。 写真は差し込み終わった後。 f:id:masaibar-dev:20170614000854p:plain

後はレバーを元の位置に下げ、メモリ格納部分の蓋を締めてからコンセントに繋いで起動したら左上のリンゴマークから「このMacについて」→「メモリ」で認識されているメモリを確認する。

無事に16GB×4枚で64GBとして認識されている、めでたしめでたし。 f:id:masaibar-dev:20170614000513p:plain

ちなみに今回使ったメモリはこちら。

Appleが発表している増設可能なメモリの規格は下記のものである。 f:id:masaibar-dev:20170614004049p:plain

これに照らし合わせると下記のメモリも該当しているし、Amazonのレビューを見る限りはiMac 2017 5Kでの動作報告もされている。

まとめ

というわけで、カスタマイズ版を注文せずとも無事に64GBメモリに増設することが出来た。

今のところメモリが余り過ぎてて32GBでも充分だったのではという思いを拭いきれないが、もう少し使ってみたら使用感を書くかもしれない。

ちなみにこの記事はMac miniで書いている、新しいマシンってワクワクするけど使い慣れてないし…

私の購入時は12,800円 × 4枚 = 51200円、公式カスタマイズの価格は154,000円だが、税抜表示なので実際には166,320円であり、引き算すると115,120円もお得になった。

今回の出費額から目を反らしつつ この差額だけでMac miniの梅モデルが2台も買えてしまうではないか。

(2017/07/17追記) iMac 2017にはHDMI端子が無いため、元々使っていたMac miniのためのモニターと繋ぐためには変換ケーブルが必要となった。

最初はPlugableのアダプタを買ったが対応しておらず返品し、結局下記の製品に互換があることが分かったため2使ってトリプルディスプレイで運用している。 いつかは4Kディスプレイを買って置き換えたいが、現状のディスプレイでも解像度は充分なので当面はこのままになるだろう。


  1. 公式スペックだと松と竹モデルのみ64GBとなっている。しかし、非公式ではあるが梅でも64GBが認識されるらしい。あくまでも自己責任で…

  2. PC4-2400は恐らく誤植であり、正しくはPC4-19200であると思われる。そもそも初期は260pinであるべき箇所が204pinと表記されており、twitterを見ると海外の方も含め混乱している人が何人か確認できた。何を隠そう私も混乱してappleサポートに確認の電話をかけた。