こんにちは。前にcurlでhttpリクエストが出来たので、今ハマっているアンドロイドアプリでも出来ないかと思い、いろいろ調べて実装してみました。
内容は、マストドン(mstdn.jp - mstdn.jp)への定型文のトゥートです。
下準備
- マストドンのどこか(自分はmstdn.jpで試しましたが他でも同様と思います)でアカウントを取得する
- メニュー→開発→新規アプリと進む
- アプリ設定
- アプリの名前を入れる
- ウェブサイトは空白でいい。リダイレクトURLもデフォルトの
urn:ietf:wg:oauth:2.0:oob
でいい - アクセス権は
write:statuses
があれば十分 - 送信、を押すと新しいアプリが登録される
- クライアントキー、クライアントシークレット、アクセストークンが発行されます。とりあえずアクセストークンが必要なものです(使える期間についてはよくわからん)
ここをアプリ内で処理するのが理想ではあります
アプリ作成
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>
- MainActivity.java
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参照
- HeaderにAuthorizationが必要。
Bearer <user token>
を入れる必要があるので、setRequestProperty("Authorization", "Bearer "+ACCESS_TOKEN)
とします(AndroidでHTTP通信したいときの手段まとめ - Qiita参照)
以下bodyに入れるもの
- status(トゥートの内容)
- visibility(公開範囲)→公開に流すと迷惑なので未収載
unlisted
にする
これをbodyに突っ込むために、setDoOutput
をtrue
にした上で//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のメソッド(トースト表示)を呼び出します。
まとめ
なかなかざっくりとしか分かってないですが動きました。
記念すべきアプリからのトゥート第一号がこちらです
アクセストークンをハードコーディングしてる時点でめちゃくちゃですけど笑
動いてよかった(こなみ
おしまい。