放浪軍師のアプリ開発局

Xamarin.Formsを使ってAndroid,iOS,UWP,WPFで動くアプリを開発したりしています。

画像を表示するライブラリFFImageLoadingを試す

放浪軍師の Xamarin.Forms によるアプリ開発。今回は画像表示用のライブラリであるFFImageLoadingを試してみようと思います。 いやぁ、ひっさびさのちゃんとした技術記事になりますね。言っとくけどこれが本業だから!

おしらせ

退職エントリー(前、中、後編)を読んでくださった方々、どうもありがとうございました。お陰様で、もの凄い反響がありました。このブログやTwitterはてなブックマークへのコメントに関しましては、すべて目を通させてもらっております。当然ながらもう退職エントリーを書くという事は無いとは思いますが、折角反響をいただいたことですし、たまには技術者目線の雑記も書いていこうかと思っておりますので、どうぞこれからもよろしくお願いいたします。

FFImageLoadingで画像表示させる

さて、今回の FFImageLoading ですが、OSS で公開されているライブラリで、Xamarin だけでなく UWP や WPF でも使用できるそうです。謳い文句としては高速で​​猛烈な画像の読み込み(Google翻訳)との事。

github.com

画像でメモリリークが発生した

FFImageLoading を使ってみたきっかけですが、画像表示に関しては以前、以下のような記事を書いたことがありますね。 www.gunshi.info それで、乱ちゃんProjectで画像を表示する際もこの Converter を使った方法で表示させていたのですが、Android で画像のある画面への遷移と戻りを繰り返すと、だんだん重くなって最終的に不正終了してしまうという現象が発生しました。エラーの内容は以下のようなものです。

f:id:roamschemer:20190821215213p:plain

OutOfMemoryError としつこく言われるという…、まぁたぶんメモリリークでしょう。それであーでもないこーでもないといつものように喚いていると、

という感じで FFImageLoading というものをyasutake nagano (@yasukko0423) | Twitter さんに教えて頂きました。いつもありがとーう!

使用方法

公式に使用方法が書かれていますので、そちらを参考にしてみてください。 github.com

Xamarin.Forms のアップデートでつまずく

はい、さっそくつまずきました。最初にNugetから

  • Xamarin.FFImageLoading
  • Xamarin.FFImageLoading.Forms

をインストールするのですが、 Xamarin.FFImageLoading.Forms をインストールした段階で以下エラーが発生。

エラー NU1605 Xamarin.Forms の 3.3.0.912540 から 3.2.0.839982 へのパッケージ ダウングレードが検出されました。別のバージョンを選択するには、プロジェクトから直接パッケージを参照してください。 BlankApp1 -> Xamarin.FFImageLoading.Forms 2.4.11.982 -> Xamarin.Forms (>= 3.3.0.912540) BlankApp1 -> Xamarin.Forms (>= 3.2.0.839982)

実はこれ、Nuget からライブラリをインストールする時たまに見かけるんですが、要するに Xamarin.Forms のバージョンが対応してないよ!という意味です。じゃあ、 Xamarin.Forms をバージョンアップすりゃいいやんとやってみると、

エラー NU1107 Xamarin.Android.Support.Compat のバージョンの競合が検出されました。この問題を解決するには、Xamarin.Android.Support.Compat 28.0.0.1 をプロジェクト BlankApp1.Android に直接インストールするか、参照します。 BlankApp1.Android -> Xamarin.Forms 4.1.0.673156 -> Xamarin.Android.Support.CustomTabs 28.0.0.1 -> Xamarin.Android.Support.Compat (= 28.0.0.1) BlankApp1.Android -> Xamarin.Android.Support.Design 27.0.2.1 -> Xamarin.Android.Support.Compat (= 27.0.2.1).

ってな感じになり、じゃあ、 Xamarin.Android.Support.Compat をインストールしてみると、

エラー NU1107 Xamarin.Android.Support.Compat のバージョンの競合が検出されました。この問題を解決するには、Xamarin.Android.Support.Compat 28.0.0.1 をプロジェクト BlankApp1.Android に直接インストールするか、参照します。 BlankApp1.Android -> Xamarin.Android.Support.v7.AppCompat 28.0.0.1 -> Xamarin.Android.Support.Compat (= 28.0.0.1) BlankApp1.Android -> Xamarin.Android.Support.Design 27.0.2.1 -> Xamarin.Android.Support.Compat (= 27.0.2.1).

と無限ループに陥ります。これ実は Xamarin.Forms は単独ではバージョンアップできない というのが理由で、Xamarin.Forms をアップデートする際は、関連するパッケージも同時に選択してアップデートする必要があります。やるならこんな感じですね。
f:id:roamschemer:20190821225432p:plain:w350

  1. 更新プログラムを選択
  2. すべてのパッケージを選択にチェック
  3. 更新ボタンを押す

これだけです。もしアップデートしたくないものがあるなら、チェックを外してから更新ボタンを押します。

最新版ではエラー

さて、Xamarin.Forms をアップデートすると、

  • Xamarin.FFImageLoading
  • Xamarin.FFImageLoading.Forms

をインストールできるようになります。…が、そのまま UWP をビルドすると、
f:id:roamschemer:20190821230245p:plain
とエラーが出て実行できません。どうやら現時点の最新バージョンである Ver 2.4.11.982 に不具合がある模様ですので、 UWP も使う場合は一つ前の Ver 2.4.10.972 をインストールしてみてください。その際は、両方を同じバージョンにしないとそれはそれでエラーになるので気を付けてください。

下準備

FFImageLoadingを使うには下準備が必要です。各プラットフォームにて、以下の記述を追加してください。
Android MainActivity.cs

protected override void OnCreate(Bundle bundle)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    base.OnCreate(bundle);

    global::Xamarin.Forms.Forms.Init(this, bundle);
    FFImageLoading.Forms.Platform.CachedImageRenderer.Init(enableFastRenderer:true);  //ここ
    LoadApplication(new App(new AndroidInitializer()));
}

iOS AppDelegate.cs

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();
    FFImageLoading.Forms.Platform.CachedImageRenderer.Init(); //ここ
    LoadApplication(new App(new iOSInitializer()));

    return base.FinishedLaunching(app, options);
}

UWP AppDelegate.cs

protected override void OnLaunched(LaunchActivatedEventArgs e)
{


    Frame rootFrame = Window.Current.Content as Frame;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        rootFrame.NavigationFailed += OnNavigationFailed;

        Xamarin.Forms.Forms.Init(e);
        FFImageLoading.Forms.Platform.CachedImageRenderer.Init(); //ここ
        if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            //TODO: Load state from previously suspended application
        }

        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
    }

    if (rootFrame.Content == null)
    {
        // When the navigation stack isn't restored navigate to the first page,
        // configuring the new page by passing required information as a navigation
        // parameter
        rootFrame.Navigate(typeof(MainPage), e.Arguments);
    }
    // Ensure the current window is active
    Window.Current.Activate();
}

これ系は基本 Xamarin.Forms.Forms.Init() の直下に記載ですね。ちなみにこの記述を忘れた場合、エラーが出ないが画像の表示はできないので原因がわからずハマります。注意しましょう。

画像を PCL に配置して埋め込みリソースにする

そのまんまですね。くれぐれも埋め込みリソースに指定するのを忘れないようにしてください。これも先ほどと同じ理由でハマります。
f:id:roamschemer:20190821231958p:plain

View

ViewのXamlは以下のようにします。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="FFImageLoadingTest.Views.MainPage"
             xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
             Title="{Binding Title}">

    <ScrollView>
        <StackLayout>
            <Label Text="anime.gif" />
            <ff:CachedImage Source="resource://FFImageLoadingTest.Image.anime.gif" />
            <Label Text="rancyan.png" />
            <ff:CachedImage Source="resource://FFImageLoadingTest.Image.noanime.png"/>
            <Label Text="noanime.gif" />
            <ff:CachedImage Source="resource://FFImageLoadingTest.Image.noanime.gif"/>
            <Label Text="noanime.jpg" />
            <ff:CachedImage Source="resource://FFImageLoadingTest.Image.noanime.jpg"/>
        </StackLayout>
    </ScrollView>

</ContentPage>

xmlns:ffのところで名前空間を指定。レイアウトでは
<ff:CachedImage Source="resource://FFImageLoadingTest.Image.anime.gif" />
という風に頭に resource: を付けるのが特徴ですね。他はImageプロパティと同じで配置位置などを変更できます。

注意点

.gif の表示に難があるらしく UWP では .gif がアニメーションでも静止画でも表示できません。また、Android ではアニメーション .gif が表示されたりされなかったりします。gifは避けた方が賢明ですね。

使い心地

圧倒的に軽いです!乱ちゃんProjectで発生していたメモリリークも発生しなくなりました。また、 ListView に並べた場合はこんな感じでスクロールの表示スピードが雲泥の差なので、そういった部分では活躍しそうですね。
f:id:roamschemer:20190822231308g:plain

GitHub

一応あげとります。比較も出来るので参考になれば。 github.com