おはやし日記

テーマ……バイク←プログラミング←旅

AndroidアプリでHTTP通信(POST)

こんにちは。前にcurlでhttpリクエストが出来たので、今ハマっているアンドロイドアプリでも出来ないかと思い、いろいろ調べて実装してみました。

内容は、マストドン(mstdn.jp - mstdn.jp)への定型文のトゥートです。

下準備

APIを叩くときに必要なアクセストークンをゲットします。

  1. マストドンのどこか(自分はmstdn.jpで試しましたが他でも同様と思います)でアカウントを取得する
  2. メニュー→開発→新規アプリと進む
  3. アプリ設定
    • アプリの名前を入れる
    • ウェブサイトは空白でいい。リダイレクトURLもデフォルトのurn:ietf:wg:oauth:2.0:oobでいい
    • アクセス権はwrite:statusesがあれば十分
    • 送信、を押すと新しいアプリが登録される
  4. クライアントキー、クライアントシークレット、アクセストークンが発行されます。とりあえずアクセストークンが必要なものです(使える期間についてはよくわからん)

ここをアプリ内で処理するのが理想ではあります

アプリ作成

AndroidStudioでEmptyActivityを雛形にして作ります

ちょっと古い情報を参考にしているのでメモリリークするかもみたいな感じらしいですがとりあえずは単純な処理しかしないので無視します

  • AndroidManifest.xml

インターネット通信を許可します

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.tootnyoapplication">

    <uses-permission android:name="android.permission.INTERNET" />

    <application......(略)/>

</manifest>
package com.example.tootnyoapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    HttpRequestAsync req = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                // http://9ensan.com/blog/smartphone/android/android-asynctask-executed-only-once/
                // 毎回Asyncを作り直す
                setNewAsync();
                req.execute();
            }
        });
    }

    private void setNewAsync(){
        req = new HttpRequestAsync(this);
    }

    public void showToast (String result){
        Toast.makeText(this, result, Toast.LENGTH_LONG).show();
    }
}
  • HttpRequestAsync.java

これがAsyncTaskを利用した、HTTP通信をするためのクラスです。HTTP通信は非同期でやらなければいけないそうなので。

package com.example.tootnyoapplication;

import (略)

public class HttpRequestAsync extends AsyncTask<Void, Void, String> {

    MainActivity main_ = null;

    HttpRequestAsync(MainActivity mainActivity){
        main_ = mainActivity;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // doInBackground前処理
    }

    @Override
    protected String doInBackground(Void... params) {
        HttpURLConnection con = null;
        URL url = null;
        String mstdnURL = "https://mstdn.jp/api/v1/statuses";
        String returnString = null;

        try{
            url = new URL(mstdnURL);
            con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("POST");
            con.setInstanceFollowRedirects(false);
            con.setDoInput(true);
            con.setDoOutput(true);

            // https://stackoverflow.com/questions/20020902/android-httpurlconnection-how-to-set-post-data-in-http-body/20021028
            String ACCESS_TOKEN = "ここにアクセストークンを入れる";

            String TOOT = "#にょ by app \n"+getNowDate();

            //header
            con.setRequestProperty("Authorization", "Bearer "+ACCESS_TOKEN);

            //body
            String str = String.format("status=%s&visibility=unlisted", TOOT);
            byte[] outputInBytes = str.getBytes(StandardCharsets.UTF_8);
            OutputStream os = con.getOutputStream();
            os.write( outputInBytes );
            os.close();

            //接続
            con.connect();

            //レスポンス
            // https://qiita.com/sansuke05/items/d6262cae0a2a65ca6f93

            int status = con.getResponseCode();
            switch(status){
                case HttpURLConnection.HTTP_OK:
                    returnString = "OK!";
                    break;

                default:
                    returnString = "ERROR!";

                    InputStream in = con.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    String line;
                    String readStr = new String();

                    while (null != (line = reader.readLine())){
                        readStr += line;
                    }

                    Log.d("Error", readStr);
                    in.close();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        con.disconnect();
        return returnString;
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
        // doInBackground後処理
        main_.showToast(result);
    }

    // https://qiita.com/zuccyi/items/d9c185588a5628837137
    private static String getNowDate(){
        final DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        final Date date = new Date(System.currentTimeMillis());
        return df.format(date);
    }
}
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="#にょ"
        android:textSize="48sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

やってること

MainActivity

MainActivityでやってること

  • onCreateでボタンにクリックした時の処理を書く
    • Asyncのクラスを作る
    • execute()を呼び出して非同期処理を開始する

一回作ったクラスを使い回すことはできないので、都度作り直しています。

HttpRequestAsyncクラス

HttpRequestAsyncでやってること

APIの要求

statuses - Mastodon documentation参照

以下bodyに入れるもの

  • status(トゥートの内容)
  • visibility(公開範囲)→公開に流すと迷惑なので未収載unlistedにする

これをbodyに突っ込むために、setDoOutputtrueにした上で//bodyの処理をやります。(Android HTTPUrlConnection : how to set post data in http body? - Stack Overflow)参照。

connect()のところで接続を開始します。そのあと帰ってきたレスポンスについて処理をしています。ここの処理にsetDoInput(true)が必要。

disconnect()はなんとなくやってる

Asyncの処理

参考: AndroidからEmotion APIを使ってみる - Qiita

コンストラクタを呼び出すときにMainActivityを渡しておきます。このへんがメモリリークするぞと警告が出るが気にしない。

MainActivity(UIスレッド)側でexecute()を実行するとonPreExecuteのあとにdoInBackgroundを別スレッドで実行し、終わったらonPostExecuteが実行されます。

で、onPostExecuteでMainActivityのメソッド(トースト表示)を呼び出します。

まとめ

なかなかざっくりとしか分かってないですが動きました。

記念すべきアプリからのトゥート第一号がこちらです

アクセストークンをハードコーディングしてる時点でめちゃくちゃですけど笑

動いてよかった(こなみ

おしまい。

プライバシーポリシー ・お問い合わせはこちら