この記事には、スマートサービスSMSハンドラーの内部構造の説明が含まれています。
アプリケーションは着信SMSを解析し、それらからの重要な情報のみを表示します。
美しく、すばやく、便利に表示されます。
1.仕組み
マニフェストでSMSを受信して読み取る許可を登録する
<uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.READ_SMS"/>`
receiver
登録receiver
テストにaction_sms_received_test
パーミッションaction_sms_received_test
必要です。
テスト中に実際のSMSにお金を費やさないために、アプリケーションからこのアクションを使用してIntentを送信し、キャッチします。
<receiver android:name=".receivers.SmsReceiver"> <intent-filter android:priority="2147483647"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> <action android:name="action_sms_received_test"/> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver>
これで、受信者はすべての着信メッセージを受信します
@Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case ACTION_SMS_RECEIVED: handleIncomingSms(context, intent); break; case ACTION_SMS_RECEIVED_TEST: // do test break; } }
次に、メソッドhandleIncomingSms(context, intent);
どのようなSMSが届いたかを把握し、何をすべきかを決定する必要があります。
公式であれば、分析して有用な情報を取得し、美しい形で表示します。
彼女が公式かどうかをどのように理解するか—後で説明します。
失礼、こんな感じ
private void handleIncomingSms(Context context, Intent intent) { Li("handleIncomingSms"); Bundle bundle = intent.getExtras(); if (bundle == null) { return; } try { Object[] pdus = (Object[]) bundle.get(PDUS); String smsText = ""; for (Object pdu : pdus) { final SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu); smsText += message.getMessageBody(); } checkTemplates(context, smsText); } catch (Exception e) { Li("handleIncomingSms - Exception", Log.getStackTraceString(e)); } }
メソッドcheckTemplates();
private void checkTemplates(Context context, String smsText) { Li("checkTemplates", smsText); // get templates List<SmsTemplate> smsTemplates = DatabaseManager.getSmsTemplates(); if (smsTemplates == null) { return; } // check if sms text according to some template for (SmsTemplate smsTemplate : smsTemplates) { List<String> messageLines = SmsNewParser.getMessageLines(smsTemplate, smsText); if (messageLines != null) { Sender sender = DatabaseManager.getSender(smsTemplate.sender); showPopupDialog(context, messageLines, sender != null ? sender.iconUrl : ""); } } }
showPopupDialog
メソッド
private void showPopupDialog(Context context, List<String> message, String iconUrl) { Li("showPopupDialog", message, iconUrl); Intent popupIntent = new Intent(context, PopupActivity.class); popupIntent.putExtra(PopupActivity.ICON_URL, iconUrl); popupIntent.putExtra(PopupActivity.MESSAGE_0, message.get(0)); popupIntent.putExtra(PopupActivity.MESSAGE_1, message.get(1)); popupIntent.putExtra(PopupActivity.MESSAGE_2, message.get(2)); popupIntent.putExtra(PopupActivity.MESSAGE_3, message.get(3)); popupIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(popupIntent); }
その後、ユーザーはそのような画面を見ます
ポイントは、有用な情報をすばやく表示することです。
2. SMS認識アルゴリズムと重要な情報の発行
2.1。 簡単に
- サーバー上にテンプレートがあります
- 各テンプレートは、a)SMSの外観b)正確に表示する内容を示します
- アプリケーションは、起動するたびにそれらを同期します
- 各着信メッセージは、すべてのテンプレートで実行されます。
- 一致するテンプレートが見つかった場合、重要な情報が必要なフォームに表示されます。
2.2。 モデルの詳細
パターンはこんな感じ
{ "sender": "bank_alfa", "text": "3*8272; Pokupka; Uspeshno; Summa: 212,30 RUR; Ostatok: 20537,96 RUR; RU/MOSKVA/GETT; 15.04.2016 06:02:43", "mask": "~N~*~N4~; ~BANK_ACTION_0~; Uspeshno; Summa: ~SUM_0~ ~CURRENCY_0~; ~BANK_ACTION_1~: ~SUM_1~ ~CURRENCY_1~; ~WORD~; ~N2~.~N2~.~N4~ ~N2~:~N2~:~N2~", "lines": [ { "line": "EXTRA_PURCHASE" }, { "line": "SUM_0" }, { "line": "EXTRA_TOTAL" }, { "line": "SUM_1" } ] }
-
sender
-送信者 -
text
このSMSの初期テキスト。 テストに使用できます -
mask
テンプレート自体。~FOO~
という形式のサービスワード~FOO~
-
lines
画面に表示されるメッセージの行。 テンプレートの一部を指定するか、テンプレートにない単語を使用できます。
サービスワードは、 extra
単語と通常の単語に分かれています。
Extra
は、テンプレートにないことを意味します。
例:
~SUM~
は一般的なサービスワードです。 ピリオドまたはコンマで区切られた数値を含む式を意味します。
金額を決定するために使用されます。 検索するには、正規表現を使用します
{ "name": "SUM", "regex": "\\d+[.,]{0,1}\\d+", "values": [], "is_extra": false }
~CURRENCY~
は、いくつかの意味を持つことができる普通の単語です。 検索するには、値を繰り返し処理します。
{ "name": "CURRENCY", "regex": "", "values": [ { "value": "usd" }, { "value": "rur" }, { "value": "eur" }, { "value": "rub" } ], "is_extra": false }
~EXTRA_CODE_WORD~
タイプがextra
サービスワード。 結果としてテキスト「コードワード」を表示するために使用されます。
{ "name": "EXTRA_CODE_WORD", "regex": "", "values": [ { "value": " " } ], "is_extra": true }
メッセージの送信者を正確に示す写真も必要です。
この情報はsender
オブジェクトに保存されます。
例:
これがアルファバンクとそのアイコンです。
{ name: "bank_alfa", icon_url: "https://dl.dropboxusercontent.com/u/1816879/CaptainSms/logo_alfa.png" }
その結果、サーバーは保存されません
- パターン
- サービスワード
- 送信者
2.3。 アルゴリズムの詳細
モデルをダウンロードして保存します。
次に、SMSを解析し、結果のメッセージを作成する手順に従います。
メッセージテキストを解析するには、静的メソッドでSmsParser
クラスを使用します。
メインメソッドはgetMessageLines(SmsTemplate smsTemplate, String realSmsText)
すべて問題ない場合はメッセージ行を返し、適切なテンプレートが見つからなかった場合はnull
返します。
このメソッドは、上記のcheckTemplates
メソッドのこの場所から呼び出されます。
// check if sms text according to some template for (SmsTemplate smsTemplate : smsTemplates) { List<String> messageLines = SmsNewParser.getMessageLines(smsTemplate, smsText); if (messageLines != null) { Sender sender = DatabaseManager.getSender(smsTemplate.sender); showPopupDialog(context, messageLines, sender != null ? sender.iconUrl : ""); } }
データベースからすべてのテンプレートをmessage lines
、それぞれのmessage lines
を取得しようとしmessage lines
。
判明した場合、画面に情報が表示されます..
getMessageLines
ロジックを簡単に
マスクを実行し、文字ごとにSMSテキストと比較し、検出されたサービスワードの値を配列に書き込むか、矛盾がある場合はnull
スローしnull
getMessageLines
ロジックの詳細:
- キャラクターマスクでキャラクターを走らせます
- シンボルがサービスワード(
~
)の始まりである場合:
-私たちはそれがどんな種類の単語であるかを理解しています(たとえば、〜SUM_0〜)
255.00
テキストで値を計算します(たとえば、255.00
)
-マスクから単語を切り取り、テキストからこの値を(文字ごとに実行し続けるために) - それ以外の場合、単純な文字の場合:
-それらが最大値とテキストで一致する場合、そこからそれらをカットしてさらに比較します
-それらが異なる場合、null
をスローしnull
-テキストはテンプレートに適合しません
コード例のあるロジック
パラメーターとして、メソッドにsmsテンプレートとテキストを取得します
public static List<String> getMessageLines(SmsTemplate smsTemplate, String smsText)
メソッドの最初で、サービスワードのリストを初期化します。 APIを使用した定期的な更新からデータベースにアクセスしました。
グローバル変数が必要です。なぜなら メソッドは大きく、断片に分割されます。
private static void initReservedWords() { Li("initReservedWords"); mReservedWords.clear(); mReservedWords = DatabaseManager.getReservedWords(); }
次に、指定されたテンプレートからサービスワードのリストを作成します。
List<ReservedWord> reservedWords = new ArrayList<>(); for (SmsTemplateLine line : smsTemplate.lines) { reservedWords.add(getReservedWordByName(line.line)); }
つまり テンプレートがある場合
{ "sender": "bank_alfa", "text": "3*8272; Pokupka; Uspeshno; Summa: 212,30 RUR; Ostatok: 20537,96 RUR; RU/MOSKVA/GETT; 15.04.2016 06:02:43", "mask": "~N~*~N4~; ~BANK_ACTION_0~; Uspeshno; Summa: ~SUM_0~ ~CURRENCY_0~; ~BANK_ACTION_1~: ~SUM_1~ ~CURRENCY_1~; ~WORD~; ~N2~.~N2~.~N4~ ~N2~:~N2~:~N2~", "lines": [ { "line": "EXTRA_PURCHASE" }, { "line": "SUM_0" }, { "line": "EXTRA_TOTAL" }, { "line": "SUM_1" } ] }
次に、リストを取得したい
- EXTRA_PURCHASE
- SUM_0
- EXTRA_TOTAL
- SUM_1
次はメインロジックです
// check match symbol by symbol try { do { String s = mask.substring(0, 1); if (s.equals(ReservedWord.SYMBOL)) { // found start of a reserved word ReservedWord currentReservedWord = getFirstReservedWord(mask); String valueOfCurrentReservedWord = getValueOfReservedWord(smsText, mask, currentReservedWord); // add value in the list, if reserved word is in the list if (reservedWords.contains(currentReservedWord) && valueOfCurrentReservedWord.length() > 0) { values.put(currentReservedWord.getForm(), valueOfCurrentReservedWord); } // cut text and mask to look next symbols smsText = smsText.substring(valueOfCurrentReservedWord.length()); mask = mask.substring(currentReservedWord.getForm().length()); } else if (s.equals(smsText.substring(0, 1))) { // that symbols matches, go to the next symbol smsText = smsText.substring(1); mask = mask.substring(1); } else { /* * that symbol does not match, so text not match that mask, so method fails * because we cannot return correct values according to that list of reserved word */ return null; } } while (mask.length() > 0); } catch (StringIndexOutOfBoundsException e) { /* * There is some error during parsing. * That mean text does not match mask. */ Li(TAG, "getMessageLines - Exception - " + Log.getStackTraceString(e)); return null; }
彼女は、「 getMessageLines
Logic more:」として、上記の説明を正確に実行します。
次に、リストを再ソートします。 テキストでは、 message lines
とは異なる順序で発生しmessage lines
// convert list to the right order List<String> valuesList = new ArrayList<>(); for (ReservedWord word : reservedWords) { LLog.e(TAG, "getMessageLines - return list - " + values.get(word.getForm())); if (values.get(word.getForm()) != null) { valuesList.add(values.get(word.getForm())); } }
次に、タイプextra
補助語を追加します。 SMSのテキストを通過したときにそれらを見つけられませんでした。
// add values of all the extra words for (int i = 0; i < reservedWords.size(); i++) { if (reservedWords.get(i).isExtra) { valuesList.add(i, reservedWords.get(i).values.iterator().next().value); } }
これが必要な理由です。
入り口でsmsTemplate
が与えられsmsTemplate
。 messageLineのセットがあります。 たとえば、4つありました。
"lines": [ { "line": "EXTRA_PURCHASE" }, { "line": "SUM_0" }, { "line": "EXTRA_TOTAL" }, { "line": "SUM_1" } ] }
しかし、テンプレートと一致するテキストをチェックするプロセスで、 SUM_0
とSUM_1
のみが見つかりました
なぜなら これは、SMSのテキストに実際に含まれるデータです。
したがって、最初のロジックの後に、2つの要素の配列があります(この場合、 212,30
と20537,96
)。
しかし、出力に4行を送信する必要があります(これら2つにEXTRA_PURCHASE
とEXTRA_TOTAL
を追加する必要があります)。さらに正しい順序で。
したがって、メソッドの最後にそれらを追加します。
その結果、出力では4行の配列を取得します。
たとえば、テンプレートがある場合
{ "sender": "bank_alfa", "text": "3*8272; Pokupka; Uspeshno; Summa: 212,30 RUR; Ostatok: 20537,96 RUR; RU/MOSKVA/GETT; 15.04.2016 06:02:43", "mask": "~N~*~N4~; ~BANK_ACTION_0~; Uspeshno; Summa: ~SUM_0~ ~CURRENCY_0~; ~BANK_ACTION_1~: ~SUM_1~ ~CURRENCY_1~; ~WORD~; ~N2~.~N2~.~N4~ ~N2~:~N2~:~N2~", "lines": [ { "line": "EXTRA_PURCHASE" }, { "line": "SUM_0" }, { "line": "EXTRA_TOTAL" }, { "line": "SUM_1" } ] }
次に、出力で取得します
- 購入する
- 212.30
- 残った
- 20,537.96
ここでメインロジックが終了します。
次に、このメソッドを使用してアクティビティポップアップに表示します。
showPopupDialog(context, messageLines, sender != null ? sender.iconUrl : "");
messageLines
テキストmessageLines
、テキストビューに単に表示されます。
iconUrl
、 Glideを使用して画像ビューにロードされます-ここではすべてが非常に簡単です。
おわりに
明らかに、アルゴリズムは原始的であり、改善することができます。
アイデアの
- APIを異なるJSONファイルに分割します(たとえば、送信者ごとに1つのJSON)
- スマートなテンプレート実行アルゴリズム(最初にコードを使用-最速で必要とされ、次に頻繁に使用され、次にその他すべてのアルゴリズム)
- おそらく、解析コード自体を改善することができます(冗長オブジェクトの作成の確認、ループの数の削減など)。
しかし、アプリケーションは問題を解決します。
メッセージを解析するためのメインクラスを囲みます。
上記のコードとは少し異なります。
なぜなら 上記のコードは視覚的に改善されています。