Hatena::ブログ(Diary)

hishidaのblog このページをアンテナに追加 RSSフィード

プロフィール

hishida

hishida

EB series support page 管理人 ブログ

2017-03-21

[][] 読書尚友とEBPocket for Androidをsplit-screenに対応させた

Android7.0 Nougatからsplit-screenの機能が加わっているが、読書尚友とEBPocket for Androidをsplit-screenに対応させてみた。

といっても日常的に使用しているZenfone 3 laserにはまだAndroid7.0アップデートが来ないので、エミュレータでの動作確認になる。

(あまりアップデートが遅れるようだと、初めからAndroid7.0が搭載された格安SIMフリーのnova liteあたりに買い換えたほうがいいかもしれない)

実はAndroid 7.0 に対応していないアプリでもsplit-screenは使用できるが、"app may not work with split-screen."というメッセージが表示されてしまう。

Android 7.0 Split-screen対応

マルチ ウィンドウのサポート | Android Developers

Split-screenにする要件は、

  • 画面のサイズが動的に変更されても画面のパーツが正常に表示されること。読書尚友もEBPocketも画面の回転に対応しているので、これはクリアしている。
  • targetSdkVersionを24(Android7.0)以上にする
  • manifestsでapplicationかactivityに、android:resizeableActivity="true"を記述する。

エミュレータでの実行結果は次の通り。

f:id:hishida:20170321174708p:image

f:id:hishida:20170321174704p:image

縦横でEBPokcetのレイアウトが変わっていることがわかる。読書尚友で単語を選択してEBPocketでクリップボード検索で辞書を引くこともできる。これはEBシリーズ全体でやりたかったことのゴールに近い。

さてここで一つ問題があり、targetSdkVersionをAPI24(Android7.0)以上にするということは、API23(Android 6.0)で導入された新しいパーミッションの考え方に対応しないといけないということ。

どちらかというと、こちらの作業のほうが大変だった。

Android 6.0 パーミッション対応

実行時のパーミッション リクエスト | Android Developers

Android 5.x以前のパーミッションの考えかたは、アプリインストール時に一括で許可を与えるものだったが、Android6.0からは、パーミッションを使用するときに個別に許可・不許可できるようになった。

例えば、「カメラは許可するが位置情報の使用は許可しない」とかを選択できるようになった。

読書尚友、EBPocketの場合は、WRITE_EXTERNAL_STORAGEのパーミッションが必要になる。

パーミッションがあるかどうかを確認し、ない場合は要求するコードは次の通り。

//	権限があるかどうか確認
int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);

if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
	// Should we show an explanation?
	if (ActivityCompat.shouldShowRequestPermissionRationale(this,
			Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
		// 説明が必要な場合。EBPocketの場合はパーミッションが必須なので要求
		ActivityCompat.requestPermissions(this, new String[]{
				Manifest.permission.WRITE_EXTERNAL_STORAGE
		}, REQCODE_PERMISSION);
	} else {
		// 説明が不要な場合。パーミッション要求する
		ActivityCompat.requestPermissions(this, new String[]{
				Manifest.permission.WRITE_EXTERNAL_STORAGE
		}, REQCODE_PERMISSION);
	}
	return;
}

パーミッション要求の結果はコールバックされる。

/**
 *
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
@Override
public void onRequestPermissionsResult(int requestCode, String permissions, int grantResults) {
	switch (requestCode) {
		case REQCODE_PERMISSION: {
			if (grantResults.length > 0
					&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
				// パーミッションの取得に成功した。
				// パーミッションが必要な処理をここに書く

			} else {
				// パーミッションの取得に失敗した

			}
		}
	}
}

2017-03-09

[][] C配列Objective-Cオブジェクトを保存するのは危険?

やっと安定したと思っていたEBPocket for iOSだが、複合検索で外字を選択すると異常終了するという報告をいただいた。

EBPocket / EBWin サポート掲示板

エミュレータで調べたところ、32bit OSでは問題が起きず、64bit OSだけ異常終了するらしい。NSStringの文字列がいつの間にかautoreleaseされて不正参照になっていることまでわかったが、原因がわかるまでしばらく時間がかかった。

結論的には、C配列にNSString*のオブジェクト参照を入れていたためだった。

問題の個所はこんな感じで、NSString*の配列m_keywordをクラス変数として静的に確保していた。

#define	MAX_CPLX_GRP	10
@interface ComplexSearchViewController : UIViewController 
{
	//	省略

	NSString*m_keyword[MAX_CPLX_GRP];
}

クラスオブジェクト生存中は、クラス変数のm_keyword[]に代入した文字列生存するものだとなんとなく思いこんでいた。

Objective-Cのメモリ管理の基本であるalloc/init/retain/releaseなどについては、一応理解しています)

だがよく考えてみれば、m_keyword[]は単なるC配列なので、代入してもNSStringの参照カウントは増えない。これではどこかのタイミング(関数の出口など)でautoreleaseされるのは当然だ。

そこで次のようにC配列をやめてNSMutableArrayにしてみた。NSMutableArrayなら代入すれば参照カウントがインクリメントされてautoreleaseされなくなるのではないか。

NSMutableArrayでは C配列と同様に 変数名[添字] の形で代入や参照が書けるので、ソースの修正は最小限にできる(本当はreplaceObjectAtIndex:withObject:みたいな長ったらしい名前のメソッドがある。[]はいわゆるシンタックスシュガー)。

@interface ComplexSearchViewController : UIViewController 
{
	//	省略

	NSMutableArray*m_keyword;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        // Custom initialization
        m_keyword = [[NSMutableArray alloc]initWithCapacity:MAX_CPLX_GRP];
    }
    return self;
}

- (void)dealloc {
	//	省略
    [m_keyword release];
	
    [super dealloc];
}

案の定、これで落ちなくなった。万歳。

Objective-Cはメモリ管理に気を使う。

今はiOS向けの新規案件は、より進化した言語であるSwiftの利用が増えているのではないだろうか。私もこれから何かiOS向けに書くとすれば、Swiftを選ぶ。読書尚友のiOS版をSwiftで作ってみてもいいかもしれない。

2017-02-12

[]EBWin4,EBMacに「全ての項目の表示」を実装

EBWin4、EBMacに、「検索に一致した全ての項目の表示」機能を追加した。(EBPocket for iOS/Androidも順次対応予定)

これまではEBシリーズの本文の表示モードには、連続表示と項目毎表示しかなかった。

連続表示とは、見出し語の本文を表示する場合に、後続の本文のテキストを続けて表示するモードである。

通常の連続表示の例:検索語 detectiveの後続のテキストを続けて表示

f:id:hishida:20170212101225p:image

EPWINGはもともと「電子書籍」を目指しており、一冊の本、または巻物のように、最初から最後まで通読できるようになっている。このため、EPWINGの公式ビューアであるCDView(富士通)、Viewing(イースト)、こととい(岩波書店)では、基本的に連続表示を行うようになっていた。(今ではWindows10にインストールできるかどうかも定かでない)

項目毎表示は、検索に一致した見出し語の本文だけを表示するモードである。ただしEPWINGには項目の区切りという概念がないので、ソフトウェアで何の識別子を項目の区切りにするかを決定しないといけない。これはたぶんUnix上のEPWING検索システムや、DDWinのようなサードパーティ製のEPWINGビューアで出てきた概念だと思う。

今回追加した「全ての項目の表示」とは、検索に一致した見出し語の本文だけを、すべて一覧で表示するものであり、項目毎表示の結果を連結したものと考えるといいと思う。

全ての項目の表示:検索に一致した語の本文を連結表示

f:id:hishida:20170212101244p:image

実はDDWinにはこの機能が昔からあり(項目表示→全て表示)、今でもこの機能のためにDDWinを利用しているというユーザもいらっしゃるらしい。

DDWinには一つ制約があり、串刺し検索で「全て表示」を行なった場合に、先頭の辞書しか外字が表示できなかった。EBWin4/EBMacでは全ての辞書の外字を表示するようになっている。これについては、EBWin3.xまではWin32 APIで描画していたため拡張が難しかったが、EBWin4では本文表示をWebブラウザコントロールで行っているため、実現がしやすくなった。

既知の問題点、および制約についても書いておくと、

  1. 「全ての項目の表示」では縦書き設定は解除される
  2. 「全ての項目の表示」を行なった後で、検索一致リストから項目を選択した時の挙動は、通常の連続表示/項目毎表示に戻る
  3. 串刺し検索の場合、リンクが働くのは先頭の辞書のみ (その後、全辞書のリンクが動作するように修正した)

複合検索のモードレス化

もう一つ、EBWin4で改良を行なったのは、複合検索のダイアログがこれまでモーダルポップアップだったのを、モードレスポップアップに変更した。複合検索ダイアログを表示したままで、次々と絞り込み検索をするという使い方が可能になった。

モードレスにしようとすると、ダイアログ側から、メインスレッドの表示処理をコールバックする必要がある。C++だと関数ポインタを引数で渡すような実装方法になるが、C#にはdelegateという機能があり、もう少し美しく実装できる。

モーダルダイアログの場合。OKボタンを押すまで制御が帰ってこない。OKボタンを押すとダイアログが消え、メインスレッドで検索結果を表示する。

	ComplexSearchDialog cplx_dlg = new ComplexSearchDialog();
	cplx_dlg.Owner = this;
	// モーダルダイアログとして表示
	if (dlg.ShowDialog() == DialogResult.OK)
	{
		// 検索結果を表示
	}

モードレスダイアログの場合。ダイアログを表示したまま、検索結果をメインスレッドで表示する。

	private ComplexSearchDialog cplx_dlg = null

	if ( cplx_dlg == null || cplx_dlg.IsDisposed)   // 二重起動を防ぐ
	{
		cplx_dlg = new ComplexSearchDialog();
		cplx_dlg.RefreshEvent += delegate(object sender, EventArgs e)
		{
			// 検索結果を表示
		};
		cplx_dlg.Owner = this;

		// モードレスダイアログを表示する
		cplx_dlg.Show();
	}					

ダイアログ側

public partial class ComplexSearchDialog : Form
{

	public delegate void RefreshEventHandler(object sender, EventArgs e);
	public event RefreshEventHandler RefreshEvent;

	// OKボタンが押された場合
        private void okButton_Click(object sender, EventArgs e)
        {
		//検索処理をする
		(略)
		//  検索結果をメインスレッドで表示する
		this.RefreshEvent(this, new EventArgs());
 }

2017-02-04

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

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

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

まず話の前提として、iOSアプリには「終了させる」という概念がない。(実際、アプリに終了ボタンをつけると審査でリジェクトされる)

アプリを切り替えて(1)アクティブから(2)バックグラウンドになると、しばらくして(3)サスペンドに移行し、メモリが少なくなるとiOSから自動的に終了させられ、(4)停止状態になる。

iOSアプリの状態遷移とライフサイクル - Qiita

ホームボタンを押してアプリ選択した場合、(3)サスペンドから(1)アクティブに復帰する場合と、(4)停止状態から起動されて(1)アクティブになる場合がある。このうち、後者の(4)から(1)のときに異常終了していたらしい。

これを再現しようと思うと、他にメモリを占有するアプリを多数立ち上げて、メモリ不足の状況をつくらないといけない。

私は普段はAndroidを使っていてiOSは実機デバッグでしか使用しないので、なかなか気付かなかった。

ところが最近、あるきっかけでiOSを日常的に使うようになった。

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

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

  • キクタン 【Basic】 4000 〜聞いて覚える英単語〜(アルク)
  • キクタン 【Advanced】 6000 〜聞いて覚える英単語〜(アルク)
  • キクタン 【Super】 12000 〜聞いて覚える英単語〜(アルク)
  • キクタンTOEIC(R) Test Score 600 〜聞いて覚える英単語〜(アルク)
  • キクタンTOEIC(R) Test Score 800 〜聞いて覚える英単語〜(アルク)
  • キクタンTOEIC(R) Test Score 990 〜聞いて覚える英単語〜(アルク)

iPhoneアプリ「キクタン」で、効率的に英単語学習!:アルク

それでiPod touchを毎日使うようになり、キクタンを同時に立ち上げてからEBPocketに戻ると、ユーザからの報告通りに異常終了することがわかった。

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

再現さえすれば対処が可能になる。Xcodeでデバッグモードで実行し、異常終了させてから、次の手順でクラッシュログが取得できる。

Menu→Windows→Devices

左側ペインの[DEVICES]から実機を選択し、[View Device Logs]を押すとクラッシュログが表示される。デバッグモジュールなので、異常終了したソースの箇所がわかる。

f:id:hishida:20170204105835p:image

その結果、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版は提供されていない。おかげで通勤時にキクタンを聞く習慣ができた。

2016-12-01

[][][]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に対応していない場合、文字サイズを拡大するとにじんで表示される:

f:id:hishida:20161202110628p:image

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

f:id:hishida:20161202110623p:image

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