hishidaの開発blog

EBシリーズ(EBPocket,EBWin,EBMac,EBStudio),KWIC Finder,xdoc2txt,読書尚友の開発者ブログ

EBPocket for iOS サスペンドからの復帰で異常終了する件が解決か

「EBPocket for iOS が、サスペンドからの復帰時に異常終了する」という報告が以前から上がっており、なかなか原因が分からなくて頭を悩ませていたが、どうやら解決できたと思う。

iOSアプリのプロセスのライフサイクルについて

まず話の前提として、iOSのアプリには「終了させる」という概念がない。(実際、アプリに終了ボタンをつけると審査でリジェクトされる)
アプリを切り替えて(1)アクティブから(2)バックグラウンドになると、しばらくして(3)サスペンドに移行し、メモリが少なくなるとiOSから自動的に終了させられ、(4)停止状態になる。
iOSアプリの状態遷移とライフサイクル
ホームボタンを押してアプリを選択した場合、(3)サスペンドから(1)アクティブに復帰する場合と、(4)停止状態から起動されて(1)アクティブになる場合がある。このうち、後者の(4)から(1)のときに異常終了していたらしい。
これを再現しようと思うと、他にメモリを占有するアプリを多数立ち上げて、メモリ不足の状況をつくらないといけない。
私は普段はAndroidを使っていてiOSは実機デバッグでしか使用しないので、なかなか気付かなかった。
ところが最近、あるきっかけでiOSを日常的に使うようになった。

キャンペーンでキクタンアプリを買う

昨年末頃にAppStoreのセールで、次のキクタンアプリが通常480円のところ全品120円になっており、紙の書籍より大幅に安いので、まとめ買いした。

iPhoneアプリ「キクタン」で、効率的に英単語学習!:アルク
それでiPod touchを毎日使うようになり、キクタンを同時に立ち上げてからEBPocketに戻ると、ユーザからの報告通りに異常終了することがわかった。

Xcodeでのクラッシュログの取得方法

再現さえすれば対処が可能になる。Xcodeデバッグモードで実行し、異常終了させてから、次の手順でクラッシュログが取得できる。
Menu→Windows→Devices
左側ペインの[DEVICES]から実機を選択し、[View Device Logs]を押すとクラッシュログが表示される。デバッグモジュールなので、異常終了したソースの箇所がわかる。

その結果、NSString::drawAtPoint:forWidth:withFont:lineBreakMode:で落ちていることがわかった。このメソッドはiOS7からdeprecated(非推奨)になっているもので、
これをiOS7以後の推奨メソッドの NSString::drawAtPoint:withAttributes: に変えたら落ちなくなった。

iOSは毎年メジャーバージョンが上がり、その度に使用できていたAPIが使用できなくなったりするので、メンテナンスを続けていかないとiOSのバージョンアップで使用できなくなることがある。
もしかすると二年ぐらい前から異常終了するようになっていた可能性がある。今回の修正で、失った信頼が回復できるといいのだが。

EBPocket FreeをEBPocket Basicに改称した経緯

今回提出にあたって一つトラブルがあった。Pro版はすぐに審査が通って公開されたが、EBPocet Freeの方が、アプリ名に"free"が入っていることが原因で、metadata rejectを食らった。

2. 3 PERFORMANCE: ACCURATE METADATA
Performance - 2.3.7
Your app's name to be displayed on the App Store includes references to your app’s price, which is not considered part of an app name.

Next Steps

Please remove any references to your app’s price from your app’s name, including any references to your app being free or discounted. If you would like to advertise changes to your app’s price, it would be appropriate to include this information in the app description. Changes to your app’s price can be made in the Pricing and Availability section of iTunes Connect.

アプリ名に価格を含んでいるといけないらしい。だがAppStore には free という名称を含むアプリがごまんとある。理不尽だが、Appleと戦っても勝てないので、あきらめてアプリ名を EBPocket Basic に変えて再提出したら、あっさり審査に通って公開された。
EBPocket FreeをアップデートしたらEBPocket Basicに変わってしまって驚かれるかもしれないが、これはAppleの審査のためで、内容は同じなのでご理解いただきたい。

P.S.
前述のキクタンアプリはとてもよく出来ていて、音声も収録されているので、紙の書籍よりアプリの方がいいと思う。残念ながらiOSのみで、Android版は提供されていない。おかげで通勤時にキクタンを聞く習慣ができた。

読書尚友のSDカード対応他

以前、Nexus5が故障してASUS Zenfone 3 Laser を購入した顛末を書いた。
Nexus 5 恐怖の無限ループからの脱出 - hishidaのblog
性能的には3年前のNexus 5 と際立った違いはない気もするが、Zenfone 3 Laserに変えて良かったことの一つは、外部Micro SDカードが使えるようになったことである。Nexus5 では外部SDカードによる拡張ができなかったので、拙作の読書尚友でも外部SDに青空文庫データを置くことができなかった(ということに作者が気づかなかった)。予定外の出費だったが、外部SDカードに対応できるようになったという意味では怪我の功名だったと思う。
Androidでの外部ストレージの扱いはAPIバージョンごとに変遷がある。古き良き時代はWRITE_EXTERNAL_STORAGEのパーミッションがあれば外部ストレージのどこにでも読み書きができた。ところが、kitkat Android4.4以降、外部SDカードへの書き込みが制限されるようになり、Storage Access Frameworkを使わないとSDカードにアクセスできなくなった。これは通常のファイルアクセスのAPIと異なるのが問題で、内部ストレージと同様に透過的にアクセスできないと、アプリが複雑になってしまう。
実は簡単な解決策があった。外部ストレージのパスの取得にgetExternalFilesDirを使うと、<外部ストレージのルート>/Android/data/<パッケージ名>/filesというアプリ専用のフォルダが作成される。(ここで「外部ストレージ」というのは実は外部SDカードのことではなく、内蔵メモリなのでややこしい。)
ここで外部SDカードをマウントしていた場合、外部SDカードのルート下にも同時に、/Android/data/<パッケージ名>/filesが自動的に作られる。この領域は外部SDカードであっても、アプリから読み書きが許されている。
簡単なことだが、Nexus5ではSDカードを拡張できないので、この挙動がわからなかった。
今回、読書尚友のデータ保存先を、外部SDカードのAndroid/data/<パッケージ名>/filesに変更できるようにした。これで、メモリの少ない端末の場合、青空文庫の全データをSDカードに逃がせるようになった。

EBWin4 の高DPI化について

EBWin4の高DPI化を行ったので、忘備録として作業内容をまとめておこうと思う。

事の発端は、Macbook pro retina 13" Early2015を購入したこと(2016-11-19 - hishidaのblog)。retinaディスプレイではVMWare Fusion上のWindowsの文字が極端に小さくなり、文字サイズを拡大しないと使用できなくなった。
Macbook pro retina 13"の実解像度は2560x1600だが、Mac OS Xでは擬似解像度1280x800にスケーリングされて表示される。だがVMWare 上のWindowsではドットバイドットで表示されるので、2560x1600の実解像度のまま表示され、文字が小さくなってしまう。
「ディスプレイ設定」→「テキスト、アプリ、その他の項目のサイズを変更する:」で150%〜200%に拡大すると、DPIのスケーリングに従ってアプリも拡大されて表示される。だがEBWin4のように高DPIに対応していないアプリだと、下図のように文字がにじんで表示されてしまう。Surface Proなどの高解像度の端末では、以前から問題になっていたと思う。

図:高DPIに対応していない場合、文字サイズを拡大するとにじんで表示される:

Windows Form アプリの高DPI化作業について

高DPI化全般については、下記のマイクロソフトの田中達彦氏のブログが詳しい。
アプリの高DPI(High DPI)対応について 第1回 ~ 高DPIとは ~ – 田中達彦のブログ

WPFで開発したソフトは自動的にdpiAwareになるが、昔ながらのWindows Formで開発したソフトは、デフォルトでは高DPIに非対応となる。
Windwos FormでアプリをdpiAwareにする方法は簡単で、前述のブログにあるように、app.manifestを追加してdpiAwareをtrueにすればいい。
app.manifestは、Visual Studio でプロジェクトで右クリック→追加(D)→新規項目(W)で簡単に追加できる。
ただしdpiAwareの雛型が作られるのはVisual Studio 2013以降なので(現行のVisual Studio Community 2015もOK)、Visual Studio 2010以前のバージョンを使用している場合は、記述を手動で追加する必要がある。

app.manifestに追加した記述:

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>

基本的には、これだけでdpiAwareになり、文字サイズを拡大しても文字がにじまなくなる。
ただEBWin4の場合はEPWINGの外字をビットマップイメージで表示しており、文字サイズを変えても外字ビットマップは小さいままなので、表示のバランスが非常に悪くなる。
つまり現在のWindowsの文字サイズの倍率を取得して、ビットマップ画像も拡大しなければならない。

様々なサイトを参考にして、現在のDPIを取得するサポートクラスを追加した:(100%なら96dpiを返す)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;


namespace EBWin4
{
    public static class ScreenExtensions
    {
        public static void GetDpi(this System.Windows.Forms.Screen screen, DpiType dpiType, 
                   out uint dpiX, out uint dpiY)
        {
            try
            {
                var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
                var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
                GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
            }
            catch
            {
                dpiX = 96;
                dpiY = 96;
            }
        }

        //https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062(v=vs.85).aspx
        [DllImport("User32.dll")]
        private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);

        //https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
        [DllImport("Shcore.dll")]
        private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
    }

    //https://msdn.microsoft.com/en-us/library/windows/desktop/dn280511(v=vs.85).aspx
    public enum DpiType
    {
        Effective = 0,
        Angular = 1,
        Raw = 2,
    }
}

上記クラスを使用して、現在の文字サイズのスケールを取得する関数

        /// <summary>
        /// ScreenのDPIスケールを得る
        /// </summary>
        /// <returns></returns>
        private double GetDPIScale()
        {
            double dpiScale = 1.0;
            try {
                //  現在フォームのあるスクリーンを得る
                System.Windows.Forms.Screen s =
                    System.Windows.Forms.Screen.FromControl(this);
                uint x, y;
                s.GetDpi(DpiType.Effective, out x, out y);
                dpiScale = ( x / 96.0);
            }
            catch {
            }
            return dpiScale;
        }

DPIスケールに合わせて画像を拡大する関数:

      /// <summary>
        /// DPIに応じて拡大したImage画像を作成
        /// </summary>
        /// <param name="image"></param>
        /// <param name="dpiScale"></param>
        /// <returns></returns>
        private Image GetImageStretchedDPI(Image image, double dpiScale)
        {
            Size newSize = image.Size;
            newSize.Width = (int)(newSize.Width * dpiScale);
            newSize.Height = (int)(newSize.Height * dpiScale);

            Bitmap newBitmap = new Bitmap(image, newSize);

            image.Dispose();
            return newBitmap;

        }

これで高DPIで美しく文字が表示されるようになった。
図:高DPI化されたEBWin4

高DPI時代のEPWINGビューアとして、もうしばらく延命できることとなった。

Macbook pro retina 13” Early 2015購入記

Macbook pro のキーボードに落胆

およそ1年半ぶりのアップデートとなる新Macbook pro retina 13"/15"が発表された。新機能のTouch Barに注目が集まっているが、従来のAirの置き換えを狙ったと思われる TouchBar無しモデルも提供されている。
私のメインの開発機のMacbook pro 13" mid 2012は2.02kgもあり、持ち歩きに厳しさを感じていたので、新Macbook proが出たら買い換えるつもりでいた。ところが実際に店頭で触った結果、Macbook 12"と同じストロークの浅いキーボードにどうしても馴染めない。
「第二世代パンタグラフになって打鍵感が向上している」という触れ込みだったが、Macbook 12"と2台並べて打ち比べてみても、違いが全くわからない。軽量化されたボディと画面の明るさには魅力を感じるが、実際このキーボードでプログラミングや文章作成を行うのは非常に厳しい。
また外部 I/FがUSB-Cだけというのも、現実問題としては不便である。将来的にUSB-Cが普及することは間違いないが、変換アダプタを持ち歩かないと何もできないのは困る。特にデジカメからSDで写真を取り込む機会が多いので、SDカードスロットは残してほしかった。また電源コードもUSB-Cになったので、力が加わった場合にMagSafeのように外れないので、破損しそうで怖い。(USBバッテリで充電できるというメリットはあるが)

あえて旧モデルの整備済品を購入

結局、新モデルの購入は見送り、Apple Storeで整備済品のMacbook Pro retine 13" Early 2015 (2.9GHz/core i5/8GB/512GB SSD)を購入することにした。為替レートが反映されたこともあって、税抜143,000円と大幅に安くなったし、性能的には新モデルと同等である。
整備済品は外箱が簡略化されているが、バッテリは新品に交換されていて一年保証もつき、Apple Careにも加入できる。実際上新品と同じでお買い得である。
これまで4年2ヶ月使用したMacbook pro 13" mid 2012は、中古ショップで42,000円で買い取ってもらえた。差額約10万で買い替えできたことになる。
追加アクセサリーとして、予備で持っていたMagsafe ACアダプタを活用するために、Magsafe-Magsafe2変換アダプタを購入した。
あとはThunderboltのポートが余っていて勿体無いので、Thunderbolt-Ethernet アダプタを購入しようと思う。

Macbook Pro retine 13" Early 2015 の感想

購入して2週間ほど使用してみたが、やはりretinaディスプレーの美しさは圧倒的で、満足度は高い。特にデジカメで撮影した写真の鑑賞には非常に向いている。
1.58Kgという重量は、新Macbook proよりは重いが、これまで使用していたMacbook pro 13" mid 2012から比較すると420g軽いし、バッテリ駆動時間が長いのでACアダプタ(230g)を持ち歩かなくていいことも合わせれば、実質的に650gの軽量化になる。これはミラーレス一眼のボディ+交換レンズぐらいに相当する。
これであと最低3年は使用して、その後の買い替えはUSB-Cの普及を見てから考えようと思う。

Retinaディスプレーで一つ問題になったのは、VMWareWindowsを使用した時に、ドットバイドットで表示されるために、文字が小さすぎるということ。
文字サイズを拡大すれば解決するが、ここで拙作のEBWin4が高DPIに対応していないという問題が明らかになってしまった。調べたところ、Windows Formで開発したアプリはデフォルトでは高DPIに非対応だが、app.manifestの設定でDPIawareにできることがわかった。ということで、近日中に高DPIに対応したEBWin4を公開予定である。retinaディスプレーの思わぬ副産物である。(次のエントリに続く)

EBPocket for iOS の中華フォント現象

「iOS10から、EBPocket for iOSの本文の日本語部分のフォントが中国語の繁体字になっている」というご指摘があった。iPad mini2で確認すると、英語フォントを指定した場合、確かに中国語の字形になっている。フォントの初期値をヒラギノではなく英語フォントのTimesNewRomanPSMTにしているのは、英和辞典などで文字のバランスがよいためだが、以前は日本語部分はヒラギノで表示されていた記憶がある。

調べてみると、最近のiOSでは中華フォント現象というのがあるらしい。
iOS で日本語文章に発生する中華フォント現象とは
ただこのページで書いてある解決方法はSwiftの話で、EBPocketではObjective-CでUIWebViewを使用して本文を表示しているので、そのまま適用できない。
いろいろ試したところ、次のように、UIWebViewに表示するhtmlにlang="ja"をつければいいということがわかった。これでめでたく日本語部分がヒラギノで表示されるようになった。

<!DOCTYPE html>
<html lang='ja'>

言語の指定がないと標準で英語(lang='en')になり、iOSのフォントの優先順位で中国語が日本語より先にあるために、フォールバックで中国語フォントが選ばれるらしい。
とりあえず中華フォント現象のみ対応したバージョンをAppStoreに提出した。iOSのバージョンが上がってから初めての提出なので、審査が通るかどうかちょっと不安。
やっぱりiOSのバージョンが上がると必ず何かしら問題が起きるなぁ。

P.S.
AppStoreの審査は1日で通過し、本日リリースされた。最近は審査がスピードアップされているらしい。

読書尚友・EBPocketをマテリアルデザインに対応

Androidアプリの読書尚友とEBPocketを、マテリアルデザインに対応してみた。



マテリアルデザインiOSなどのフラットデザインに似ているが、画面のパーツが現実世界の素材のメタファになっており、厚みや奥行き、重なり、質量を持つ。
GoogleGoogle I/O 2014でマテリアルデザインガイドラインを出したが、当初はGoogle謹製のアプリのようなUIを実現するには開発者が自前でコードを書く必要があった。2015年にDesign Support Libraryが公開されたことで、マテリアルデザインに沿ったアプリを作りやすくなった。
個人的なAndroidの開発環境をEclipseからAndroid Studioに移行したことで、これらのライブラリの導入が簡単になった。もうEclipseには戻れないと思う。
Android Studio では新しいプロジェクトを作るときのテンプレートでマテリアルデザインに沿ったアプリの雛形を生成できるので、自動生成されるレイアウトやリソースを流用したほうが作業が早い。

今回のマテリアルデザイン化の作業をまとめてみると:

  • styles.xmlでマテリアルテーマを使う。
	Theme.AppCompat.Light.DarkActionBar
	Theme.AppCompat.Light.Dialog
  • 配色はマテリアルデザインガイドにあるものを使用する
  • 従来のFragmentActivityやActionBarActivityのかわりにAppCompatActivityを使う
  • ActionaBarのかわりにToolBarをつかう。(ただしAppCompatActivityで従来のActionaBarも使える)
  • Design Support Libraryの部品を使う。今回使用したクラスは、
android.support.design.widget.CoordinatorLayout
android.support.design.widget.AppBarLayout
android.support.design.widget.NavigationView
android.support.v7.widget.Toolbar
  • サポートライブラリの定義をbuild.gradleに追加
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:design:23.4.0'
    compile 'com.android.support:support-v4:23.4.0'

EBPocketのほうはマテリアルデザインの導入を機にかなりUIを見直したので、操作性が向上したと思う。

  • ActionBarメニューは本文に関するメニュー(共有、ブックマーク登録、ページ内検索)だけ残し、他はナビゲーションドロアに移動。
  • グループ選択とグループの編集がナビゲーションドロアでできるようにした。グループ切り替えが迅速にできるようなった。
  • ブックマーク、最近の項目、履歴を共有で外部アプリに渡せるようにした。
  • オーバースクロールによる前画面移動をオプションでオフにできるように。
  • SearchViewの検索履歴ポップアップをデフォルトでオフにした。

EBPocketの今後の課題

  • PDIC,MDict,StarDictでブックマークを保存できるようにする
  • App Indexngに対応する。Google検索にアプリ検索を表示する機能。従来は端末内検索という機能があったが、Kitkat以降削除され、App Indexingが推奨されるようになった。

ただしApp IndexngはWebサービスも必要で、アプリだけ登録する場合は申請フォームから申請の必要があるらしい(まだ十分に調べていない)

  • 要望の上がっているところではAnki連携。ただ私がまずAnkiになれないといけない。
  • あとは単語帳とか、英語学習的な機能を充実できれば。

読書尚友の今後の課題

  • 本棚をRecycleViewにして、カード形式とグリッド形式を切り替えられるようにする。
  • ファイルブラウザの機能向上

Macbook Pro Mid 2012 ハードディスクトラブル

どうも最近呪われているらしい。
毎日持ち歩いている相棒の Macbook Pro Mid 2012が、起動しなくなり、画面中央にクエスチョンマーク付きのフォルダが点滅表示されるようになった。どうやら起動ディスクを認識しないらしい。

サポートページの指示に従い、復旧を試みる。
Mac の起動時に「?」マークが点滅表示される場合 - Apple サポート
まず電源を押した後にcommand + Rキーを押し続けて、OS X 復元システムから起動。Apple メニューから「起動ディスク」を選択しようとするが、起動ディスク自体が表示されない。
ディスクユーティリティを開くと、内蔵の750GBのディスクドライブ自体は認識しており、パーティションも表示される。
パーティションを選択して「ディスクを修復」を実行すると、しばらくしてエラーが表示され、修復に失敗した。
この時点ではディスクの寿命による故障と判断し、ディスクを交換することを決意する。
この機会にSSDに交換することも考えたが、価格的な問題で、とりあえず1TBの2.5インチハードディスクを5,000円で購入した。
USB外付けHDDケースに入れ、ディスクユーティリティでパーティションを切り、Mac OS X を新規インストールする。
そのままUSB外付けドライブから起動させると、難なく起動に成功する。
このディスクを内蔵ディスクと交換すればそのまま使用できるはずだが、なぜか起動ディスクとして認識せず、またクエスチョンマーク付きのフォルダアイコンが表示される。
不思議なことに、確かにGUIDパーティションとしてフォーマットしているのに、内蔵SATAケーブルにつなぐとマスターブートレコードパーティションとして認識される。これが原因で起動ディスクにならないらしい。
ディスクの相性問題かと思って、手持ちのハーディスクでいくつか試した結果、日立製の320GBのディスクでは内蔵でも起動することがわかった。
もともと750GBで使用していた環境を320GBに引っ越すのは容量がかなりきついが、背に腹は変えられず、当面は320GBで運用することにした。

これで解決かと思われたが、数日すると、日立製320GBも起動ディスクとして認識しなくなり、またクエスチョンマーク付きのフォルダアイコンが表示されるようになった。320GB内蔵ディスクを取り出してUSB外付けHDDケースに入れ、外部ディスクとして起動すると、問題なく使用できるので、ディスクは壊れていない。
ここで初めて内蔵SATAケーブルを疑うようになった。
あらためてググってみると、Macbook pro Mid 2012は内蔵SATAケーブルのトラブルが多いらしいことがわかる。
MacBook Pro Mid 2012の内蔵SATAケーブルを交換する - @ビボーログ
MacBook Pro (Mid 2012, 13インチ)のSATA cableの交換 - おっぱいそん!
ジーニアスバーやApple提携プロバイダに修理を依頼すると、たぶん作業料金だけで4万円以上かかることは必至なので、ケーブルだけ入手できないかと調べたところ、Amazonで2300円程度でパーツショップから買えることがわかった。
https://www.amazon.co.jp/gp/product/B01CTZLI1S/ref=oh_aui_search_detailpage?ie=UTF8&psc=1
早速発注して内蔵SATAケーブルを取り替えてみたところ、結果は大当たりで、あっけなく復旧!
オリジナルの750GBディスクは壊れていなかったようで、ケーブル交換後に復旧を試みると、無事にMax OS Xをインストールすることができた。これで元どおりになった。

幸いTime Machineのバックアップがあったので、直近数日の作業が飛んだだけで済んだ。
教訓としては、Time Machine のバックアップは大事ですよということと、ハードディスクが認識しないときは万一の可能性として内蔵SATAケーブルを疑うべしということ。

毎日持ち歩いていることも故障の原因の一つかもしれない。2kgのMacbook Pro は流石に重いので、軽量化したいが、現行のMacbookのラインナップはそれぞれ一長一短があって、乗り換え候補が決まらない。
Retinaディスプレーは外せないのでMacbookMacbook pro Retineのどちらかになるが、Macbookはキーボードのストロークが極端に浅いのが問題で慣れそうにない。Macbook pro Retinaは重い(と言っても1.6kgなので現在のMacbook pro Mid 2012より軽いが)し、もうじき後継機の噂がある。もうしばらく悩みが続きそうである。

さて、やっと開発環境が復旧したので、何を作るかを決めないと。。。