【2023年】androidアプリからラインに通知する方法

アプリ
この記事は約14分で読めます。

アプリからLINEにメッセージを送る手段として、LINE Notifyを使用する方法がありますが、ネット検索してもWEBアプリからの実装コード例は多く見つかりますが、androidアプリからの実装コード例が、ほぼほぼ見つかりませんでした。

この記事では、わずかながらの参考記事を元に試行錯誤を繰り返し、androidアプリとLINEとの連携を実現した内容について解説します。

この記事を読むと以下のことができるようになります。

androidアプリから画像付きのメッセージがLINEに届く。以下はそのイメージ図。

アプリとLINEの連携を行うにはコード実装と人手設定を行う必要があります。設定と実装に分けて解説します。

設定

設定の流れ
  • ステップ1
    LINE Notify公式ページにログイン
  • ステップ2
    アクセストークンの発行
  • ステップ3
    LINE Notifyを友だち追加

手順に関しては、以下の記事が参考になります。

実装

実装にあたり、参考にしたのがLINEの公式ドキュメント「LINE Notify API Document」です。一読すると記事の理解が深まります。以下の表はドキュメントの抜粋になりますが、気になった点をコメントします。

リクエスト方法
リクエストメソッド/ヘッダ
MethodPOST
Content-Typeapplication/x-www-form-urlencoded
OR
multipart/form-data
AuthorizationBearer <access_token>

Content-Typeは「multipart/form-data」も使用可能とのことですが、ファイル送信する想定が無かったので試していません。

リクエストパラメータ
パラメータ名必須説明
message必須String最大 1000文字
imageThumbnail省略可能HTTP/HTTPS URL最大 240×240px / JPEG のみ許可されます
imageFullsize省略可能HTTP/HTTPS URL最大 2048×2048px / JPEG のみ許可されます
imageFile省略可能FileLINE上の画像サーバーにアップロードします。
対応している画像形式は、png, jpegです。
imageThumbnail/imageFullsizeと同時に指定された場合は、imageFileが優先されます。
1時間にuploadできる量に制限があります。
詳しくは、API Rate Limitの項を見てください。
stickerPackageId省略可能Numberパッケージ識別子。
Stickerの識別子は以下を参照ください。
Sticker一覧
stickerId省略可能NumberSticker識別子
notificationDisabled省略可能Booleantrue: メッセージ送信時に、ユーザに通知されない。
false: メッセージ送信時に、ユーザに通知される。ただし、LINEで通知をオフにしている場合は通知されません。
デフォルト値は false です。

パラメータ名は「imageFile」「notificationDisabled」も指定可能とのことですが、想定に無かったので試していません。「stickerPackageId」「stickerId」は動作確認できましたが、実装においてLINEスタンプ表示は見送りました。

実装中に悩まされたのが「imageThumbnail」「imageFullsize」です。共に省略可能とあるので「message + imageThumbnail」指定、「message + imageFullsize」指定を試しましたが正常に動作せず、結局「message + imageThumbnail + imageFullsize」指定でうまく行きました。片方だけの省略はダメなようです。

又、公式では「imageThumbnail」「imageFullsize」で指定可能なファイルがJPEGのみとなっていますが、動作確認ではPNGでも問題ありませんでした。

リクエスト周りの実装コードはこんな感じです。

try {
    URL url = new URL("https://notify-api.line.me/api/notify");
    connection = (HttpURLConnection) url.openConnection();
    // リクエストのボディ送信を許可
    connection.setDoOutput(true);
    // HTTPリクエストメソッド(POST)
    connection.setRequestMethod("POST");
    // リソースのメディア種別
    connection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    // HTTP認証ヘッダー
    connection.addRequestProperty("Authorization", "Bearer " + token);
    try (OutputStream os = connection.getOutputStream();
        OutputStreamWriter out = new OutputStreamWriter(os)) {
        out.append("message=").append(URLEncoder.encode(message, "UTF-8"));
        // ラインスタンプ付与時 out.append("&stickerPackageId=1&stickerId=403");
        out.append("&");
        out.append("imageThumbnail=").append(URLEncoder.encode(image, "UTF-8"));
        out.append("&");
        out.append("imageFullsize=").append(URLEncoder.encode(image, "UTF-8"));
        out.flush();
レスポンス本文(レスポンス本文はJSONのobject型になります)
nametypevalue description
statusnumberHTTP ステータスコードに準拠した値
200: 成功時
400: リクエストが不正
401: アクセストークンが無効
messagestring人間可読なメッセージです

レスポンスは正常時、以下の内容で取得できます。
{“status”:200,”message”:”ok”}

レスポンス周りの実装コードはこんな感じです。

        // レスポンスの受信
        try (InputStream is = connection.getInputStream();
            BufferedReader r = new BufferedReader(new InputStreamReader(is))) {
            String res = r.lines().collect(Collectors.joining());
            if (!res.contains("\"message\":\"ok\"")) {
                Log.e("ERROR", "Response NG");
                return res;
            }
        }

その他、異常系の処理の実装ですが、異常を想定した確認結果がこちらになります。

1.LINEアプリから「LINE Notify」を友だち登録削除した場合。
⇒ 処理が正常終了。LINEに通知無し。

2.LINE Notifyページからサービス連携解除した場合。
⇒ 処理が異常終了。例外検出(java.io.FileNotFoundException)

レスポンス異常となる場合が確認できませんでしたが、レスポンス異常、例外検出の両方に対応した処理(結果を画面出力)としました。

異常系周りの実装コードはこんな感じです。

ExecutorService ex = Executors.newSingleThreadExecutor();
Future<String> futureResult = ex.submit(new Comm(token, message, image));
try {
    return futureResult.get(); // 非同期処理結果を返却。
} catch (Exception e) {
    return e.getMessage(); // 例外検出内容を返却。
}

以上を踏まえて、動作確認できた実装コードを丸っと記載しておきます。実装の参考になれば幸いです。

処理側(LineNotify.java)

package <your package name>;

import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

class LineNotify {

    public String notify (String token, String message, String image) {

        ExecutorService ex = Executors.newSingleThreadExecutor();
        Future<String> futureResult = ex.submit(new Comm(token, message, image));
        try {
            return futureResult.get(); // 非同期処理結果を返却。
        } catch (Exception e) {
            return e.getMessage(); // 例外検出内容を返却。
        }
    }

    class Comm implements Callable<String> {
        private final String token;
        private final String message;
        private final String image;
        Comm(String token, String message, String image) {
            this.token = token;
            this.message = message;
            this.image = image;
        }
        @Override
        public String call() throws IOException {
            HttpURLConnection connection = null;
            try {
                URL url = new URL("https://notify-api.line.me/api/notify");
                connection = (HttpURLConnection) url.openConnection();
                // リクエストのボディ送信を許可
                connection.setDoOutput(true);
                // HTTPリクエストメソッド(POST)
                connection.setRequestMethod("POST");
                // リソースのメディア種別
                connection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                // HTTP認証ヘッダー
                connection.addRequestProperty("Authorization", "Bearer " + token);
                try (OutputStream os = connection.getOutputStream();
                    OutputStreamWriter out = new OutputStreamWriter(os)) {
                    out.append("message=").append(URLEncoder.encode(message, "UTF-8"));
                    // ラインスタンプ付与時 out.append("&stickerPackageId=1&stickerId=403");
                    out.append("&imageThumbnail=").append(URLEncoder.encode(image, "UTF-8"));
                    out.append("&imageFullsize=").append(URLEncoder.encode(image, "UTF-8"));
                    out.flush();
                    // レスポンスの受信
                    try (InputStream is = connection.getInputStream();
                        BufferedReader r = new BufferedReader(new InputStreamReader(is))) {
                        String res = r.lines().collect(Collectors.joining());
                        if (!res.contains("\"message\":\"ok\"")) {
                            Log.e("ERROR", "Response NG");
                            return res;
                        }
                    }
                }
            } catch (Exception e) {
                Log.e("ERROR", "Exception detected");
                throw e;
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
            return "";
        }
    }
}

呼出側(抜粋)

String contents = <your message>;
String image = <your site> (例)"https://<your domain>/sample.png";
String token = <your access token>;
LineNotify classln = new LineNotify();
String ret = classln.notify(token, contents, image);
if (ret.equals("")) {
    text.setText("正常終了しました。");
} else {
    text.setText("異常終了しました。\n" + ret);
}

以上です。