.NET MAUI (Android) で電波強度の取得をしてみる
はじめに
この記事は別ブログ「古事連書帖」にて記載したものの転載です。Zennでの一発目の記事として投稿します。
普段趣味で.NET MAUIでAndroidアプリを書いています。最近ふとした気持ちで「電波強度なんて取れたら楽しそう」みたいな感じになり、MAUIのAPIとしては現在のネットワーク接続が何かはわかりますが、電波強度などまではわかりません。そこで、いろいろ探したりしてなんとか形にできたので、それをまとめたものになります。
MAUIとはいえ、Androidのプラットフォーム固有のAPIばかりを書くので、おおよそ対訳みたいな感じになります。
おことわり
プラットフォーム固有のAPIを使用するので、作成するcsファイルはすべて「Platform/Android」配下に置きます。また、余計な考慮をしたくないので、この説明中の「最小ターゲットAndroidフレームワーク」は「Android 10.0 (APIレベル29)」にしてあります。
携帯ネットワーク通信の電波強度を取得する
Android API Level 31 以降とそれより前で必要な物が変わります。最低サポートバージョンを Android 12にする場合は気にしなくていいですが、それより前をサポートする場合はそれぞれに考慮する必要があります。
つまり、Android 12より前のバージョンでは、Android.Telephony
名前空間のTelephonyManager.Listen() メソッド(Android) を使用しますが、12以降のバージョンでは非推奨となり、 Android.Telephony.TelephonyManager.RegisterTelephonyCallback() メソッド(Android) を使います。
そして、前者 Listen()
メソッドで必要なコールバックを受けるクラスはAndroid.Telephony.PhoneStateListener クラス(Android) を継承し、後者「RegisterTelephonyCallback()」は Android.Telephony.TelephonyCallback クラス(Android) の継承、および Android.Telephony.TelephonyCallback.ISignalStrengthsListener インターフェース(Android) の実装をしたものを用意します。ここで、両方が必要になる場合、特に後者で必要なクラスとインターフェースはAPI Level 31から追加になったもので、30以前のOS、Android 11以前では存在しないため変数として宣言できませんので、両方に対応できるよう、別でインターフェースなどを用意しそれを型として変数宣言する方法を採るといいかもしれません。
ちなみに、この情報を取得するのに必要な権限はありません。適当に権限をなくしてエミュレーターや実機に流して実行してみましたが、特に怒られることはなかったです。
using Android.Content;
using Android.Telephony;
public class NetworkStrengthWatcher
{
/// <summary>
/// 携帯ネットワーク通信に関するコールバックをまるめるインターフェース
/// </summary>
private interface ITelephonyState
{
/// <summary>
/// 電波強度
/// </summary>
int Level { get; }
}
/// <summary>
/// Android API Level 31 以降用
/// </summary>
private class TelephonyState :
TelephonyCallback,
TelephonyCallback.ISignalStrengthsListener,
ITelephonyState
{
public int Level { get; private set; }
public void OnSignalStrengthsChanged(SignalStrength signalStrength)
{
// 電波強度を見やすいように 0 から 4 にしてくれているプロパティを使う
this.Level = signalStrength.Level;
}
}
/// <summary>
/// Android API Level 30 以前用
/// </summary>
private class TelephonyStateOlder : PhoneStateListener, ITelephonyState
{
public int Level { get; private set; }
public override void OnSignalStrengthsChanged(SignalStrength? signalStrength)
{
if (OperatingSystem.IsAndroidVersionAtLeast(31))
{
return;
}
base.OnSignalStrengthsChanged(signalStrength);
if (signalStrength is null)
{
return;
}
// 電波強度を見やすいように 0 から 4 にしてくれているプロパティを使う
this.Level = signalStrength.Level;
}
}
}
あとは、変数宣言の型はインターフェースで、実際の代入にはAPI Levelに応じたものを入れ込めばOKです。
private ITelephonyState telephonyState;
public NetworkStrengthWatcher()
{
if (OperatingSystem.IsAndroidVersionAtLeast(31))
{
this.telephonyState = new TelephonyState();
}
else
{
this.telephonyState = new TelephonyStateOlder();
}
}
つづいて、状態のコールバックを受け取るための購読処理をおこないます。携帯ネットワーク通信にかかわる情報は「Android.Telephony.TelephonyManager」クラスを使用します。これは「MainActivity」のインスタンスから取得できるので、今回作成したクラスにそのインスタンスを渡してもらい、呼び出します。
さきほどのコンストラクターを書き換えて、以下のように整えます。
private MainActivity activity;
private TelephonyManager? telephonyManager;
private ITelephonyState telephonyState;
public NetworkStrengthWatcher(MainActivity mainActivity)
{
this.activity = mainActivity;
// これで取れるはず。GetSystemService の返り値が nullable なので as を使い、
// 受け取りも nullable にしておきます
this.telephonyManager = mainActivity.GetSystemService(Context.TelephonyService) as TelephonyManager;
if (OperatingSystem.IsAndroidVersionAtLeast(31))
{
this.telephonyState = new TelephonyState();
}
else
{
this.telephonyState = new TelephonyStateOlder();
}
}
次に購読開始のメソッドを用意します。前述の通り、API Versionによって呼び出すメソッドは変わり、また必要なコールバッククラスも変わりますので、バージョンで分岐させつつ呼び出します。コールバッククラスはインターフェースでどちらが入ってもいいようにしてありましたので、ここで本来必要になる実装型に変換してあげればOKです。
public void Listen()
{
if (OperatingSystem.IsAndroidVersionAtLeast(31))
{
// 本当は MainExecutor プロパティは nullable なので見た方がいいかも。ここでは知ったこっちゃない扱いにしておきます。
this.telephonyManager?.RegisterTelephonyCallback(this.activity.MainExecutor!, (TelephonyState)this.telephonyState);
}
else
{
this.telephonyManager?.Listen((TelephonyStateOlder)this.telephonyState, PhoneStateListenerFlags.SignalStrengths);
}
}
念のため購読解除も用意しておきましょう。こちらもAPI Versionによって呼び出すメソッドが変わります。31以降の場合は UnregisterTelephonyCallback()
メソッドを呼び出すと止まりますが、それより前の場合は明確な停止メソッドがありませんので、 Listen()
メソッドのフラグに PhoneStateListenerFlags.None
を渡すことになります。
public void Stop()
{
if (OperatingSystem.IsAndroidVersionAtLeast(31))
{
// 素直なメソッドがあるのでそれを使えば止まる
this.telephonyManager?.UnregisterTelephonyCallback((TelephonyState)this.telephonyState);
}
else
{
// None を入れてリッスンすると止まる
this.telephonyManager?.Listen((TelephonyStateOlder)this.telephonyState, PhoneStateListenerFlags.None);
}
}
これで携帯ネットワーク通信の電波強度を調べる準備はOKです。あとは実際に使うところですが、いったんここではMainActivityクラス内で宣言し、リッスン開始をするようにします。
// プロジェクトを作った際のいろいろ書いてあるところは省略してあります。
public class MainActivity : MauiAppCompatActivity
{
private NetworkStrengthWatcher? networkStrengthWatcher;
protected override void OnStart()
{
base.OnStart();
this.networkStrengthWatcher ??= new NetworkStrengthWatcher(this);
// サクッとスタート
this.networkStrengthWatcher.Listen();
}
protected override void OnDestroy()
{
// ここで止める
this.networkStrengthWatcher?.Stop();
base.OnDestroy();
}
}
ここまで書けていれば、もうあとは実行すれば情報がおりてきます。ただ、このままだと実行してもログもなんも出してないので、コールバッククラスの OnSignalStrengthsChanged()
メソッド内でなんとなくログを出すようにしておくと、実行時に変化が「出力」に出てくるのでわかるかと思います。
エミュレーターでネットワークの状態をポチポチといじってあげれば、ログにもその情報が逐一入ってきているのがわかります。
おしまい
ここまでで、携帯ネットワーク通信の電波強度について取得することができました。今回はおおよそAndroidのステータス表示と同じものが出せるようになります。あとはLTEなのか5Gなのかとるかなあ…って思ってましたが、とりあえずこの辺で。
最後にここまで書いたコードの全容を置いておきます。
このほかWiFiも取れるのでそちらも書きます。頑張って2024年内に出す。
Discussion