質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.20%

AsyncTaskを使って位置情報を取りたい

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,772

yomogi259

score 7

前提・実現したいこと

Androidで位置情報をGoogleMapに描画するアプリを作成しています。その時に、LocationManagerから位置情報をAsyncTaskを用いて非同期的に取得したいと思っています。ここのサイトを参考にプログラムを作成したのですが、エラーが発生してしまいました。同期処理であればlocationmanagerを使って位置情報を取得できています。どうかご教授お願い致します。

発生している問題・エラーメッセージ

E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
                                                 Process: test.com.myapplication, PID: 6305
                                                 java.lang.RuntimeException: An error occurred while executing doInBackground()
                                                     at android.os.AsyncTask$3.done(AsyncTask.java:318)
                                                     at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
                                                     at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
                                                     at java.util.concurrent.FutureTask.run(FutureTask.java:242)
                                                     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
                                                     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
                                                     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
                                                     at java.lang.Thread.run(Thread.java:761)
                                                  Caused by: java.lang.SecurityException: "gps" location provider requires ACCESS_FINE_LOCATION permission.
                                                     at android.os.Parcel.readException(Parcel.java:1683)
                                                     at android.os.Parcel.readException(Parcel.java:1636)
                                                     at android.location.ILocationManager$Stub$Proxy.getLastLocation(ILocationManager.java:725)
                                                     at android.location.LocationManager.getLastKnownLocation(LocationManager.java:1205)
                                                     at test.com.myapplication.Asyn.doInBackground(Asyn.java:61)
                                                     at test.com.myapplication.Asyn.doInBackground(Asyn.java:16)
                                                     at android.os.AsyncTask$2.call(AsyncTask.java:304)
                                                     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                                                     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) 
                                                     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) 
                                                     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 
                                                     at java.lang.Thread.run(Thread.java:761)

該当のソースコード

package test.com.myapplication;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;


public class Asyn extends AsyncTask<Void, Void, Void> implements LocationListener {

    public Location mlocation;
    double latitude, longitude;
    private Activity MainActivity;
    private Context ContextAsync;
    public LocationManager locManAsyn;
    String pro;

    public Asyn(Context context) {
        this.ContextAsync = context.getApplicationContext();
    }

    @Override
    protected Void doInBackground(Void... voids) {
        locManAsyn = (LocationManager) ContextAsync.getSystemService(ContextAsync.LOCATION_SERVICE);
        Criteria criteria = new Criteria();
        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
        criteria.setCostAllowed(false);
        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
        pro = locManAsyn.getBestProvider(criteria, false);
        if (locManAsyn.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            pro = LocationManager.GPS_PROVIDER;
        } else if (locManAsyn.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
            pro = LocationManager.NETWORK_PROVIDER;
            /*AlertDialog.Builder alert = new AlertDialog.Builder(this);
            alert.setTitle("GPS is disabled in the settings!");
            alert.setMessage("It is recomended that you turn on your device's GPS and restart the app so the app can determine your location more accurately!");
            alert.setPositiveButton("OK", null);
            alert.show();*/
        } else if (locManAsyn.isProviderEnabled(LocationManager.PASSIVE_PROVIDER)) {
            pro = LocationManager.PASSIVE_PROVIDER;
            //Toast.makeText(ContextAsync, "Switch On Data Connection!!!!", Toast.LENGTH_LONG).show();
        }

        if (ActivityCompat.checkSelfPermission(this.ContextAsync, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this.ContextAsync, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            //return TODO;
        }
        mlocation = locManAsyn.getLastKnownLocation(pro);
        if (mlocation == null) {
            latitude = mlocation.getLatitude();
            longitude = mlocation.getLongitude();
        }
        return null;
    }

    @Override
    public void onLocationChanged(Location location) {

        if (ActivityCompat.checkSelfPermission(this.ContextAsync, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this.ContextAsync, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        locManAsyn.requestLocationUpdates(pro, 0, 0, this);
    }

    @Override
    public void onStatusChanged(String s, int i, Bundle bundle) {

    }

    @Override
    public void onProviderEnabled(String s) {

    }

    @Override
    public void onProviderDisabled(String s) {

    }

    @Override
    protected void onPreExecute() {

        super.onPreExecute();
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        onLocationChanged(mlocation);
    }
}

補足情報(言語/FW/ツール等のバージョンなど)

AndroidSrudio3.0

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

アクセス許可がないということでエラーになっているようですね。

    if (ActivityCompat.checkSelfPermission(this.ContextAsync, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
        ActivityCompat.checkSelfPermission(this.ContextAsync, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {


という処理で許可を確認するまでは良いのですが、肝心の「なければ許可を得る」という処理がありませんね。そのためにエラーになっているのではないでしょうか。また、上記のコードは論理積(&&)になっていますが、「どちらか一方の許可がなければ」という論理和(||)にした方が良さそうです。

許可を得るには、

    static final int REQUEST_PERMISSION = 1000;
    static final String[] mPermission = {
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    };


    if (ContextCompat.checkSelfPermission(this.ContextAsync, mPermission[0]) != PackageManager.PERMISSION_GRANTED ||
        ContextCompat.checkSelfPermission(this.ContextAsync, mPermission[1]) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(
            this.ContextAsync, mPermission, REQUEST_PERMISSION);
    }

のような感じにすればいいのではないでしょうか。

ActivityCompat.requestPermissionsを呼ぶと、汎用のAndroidアプリを初めて実行したときによく見るような「この端末の位置情報へのアクセスを許可しますか?」のようなダイアログが出ます。ここで「許可しない」を選ばれてしまうと結局エラーになってしまうので、そういう場合にはアプリを終了する処理を入れるとか、汎用性を持たせるためにはもっと細かい処理は必要になりますが、上記ではとりあえず「そういうことはしない!」と割りきっています。

なお、REQUEST_PERMISSIONはonRequestPermissionsResultというコールバックメソッドに渡される値になるので、これを用いなければ適当な数値で構いません。

checkSelfPermissionは本来はContextCompatの持つクラスメソッドなので、上記のコードではそのように書き換えています。ActivityCompatはContextCompatを継承しているので、元のままでも支障はないのですが、気分的な問題です(まあどちらでもいいでしょう)。

ただこの処理、AsyncTaskの中でも通ったかな?実証していないのでそこはちょっと自信がありません。もし蹴られるようなら、この作業をActivityのコードの、AsyncTaskをnewする以前の場所に移してみましょう。


「時間稼ぎ」の部分の回答のコードとして

// onProgressUpdateでLocationを受け取るので、真ん中をVoidではなくLocationにする
public class Asyn extends AsyncTask<Void, Location, Void> implements LocationListener {

    private Context mContextAsync; // 頭が大文字の変数名は好ましくないのでmを付けた

    public Asyn(Context context) {
        // getApplicationContext()を呼ばずそのまま保持する
        // この変数を用いていた他の処理はそのままでも問題ないはず
        mContextAsync = context;
    }

    @Override
    protected Void doInBackground(Void... voids) {

        // 中略

        if (ActivityCompat.checkSelfPermission(mContextAsync, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
                ActivityCompat.checkSelfPermission(mContextAsync, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {

            boolean loop = true;

            // 繰り返し処理
            while (loop) {
                mlocation = locManAsyn.getLastKnownLocation(pro);
                if (mlocation != null) {
                    latitude = mlocation.getLatitude();
                    longitude = mlocation.getLongitude();
                    // onProgressUpdate()にmlocationを渡す
                    publishProgress(mlocation);
                }

                SystemClock.sleep(1000);  // 1000ミリ秒の時間稼ぎ

                if (this.isCancelled()) {
                    loop = false;
                }
            }
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Location... values) {
        // このメソッドはUIスレッドで動くのでTextViewなどを操作できる
        TextView textView1 = ((Activity) mContextAsync).findViewById(R.id.textView1);
        TextView textView2 = ((Activity) mContextAsync).findViewById(R.id.textView2);
        textView1.setText(String.valueOf(values[0].getLatitude()));
        textView2.setText(String.valueOf(values[0].getLongitude()));
    }

    @Override
    protected void onCancelled() {
        locManAsyn.removeUpdates(this);
    }

    // 中略

}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/17 01:50

    時間稼ぎの部分とTextViewを操作するための処理を、大雑把ですが回答欄のコードで示してみました。あまり動作確認していないので自信ないけど・・・。

    doInBackground()の中でループを作り、SystemClock.sleep(1000)で1秒のウェイトをおきながらぐるぐる回しています。終了するには、Activity側でAsyncTask#cancel()というメソッドを呼べば、isCancelled()がtrueになるので、このループから脱出できます。

    getLastKnownLocation()でLocationを得たら、publishProgress()でそれを渡します。これがonProgressUpdate()の引数に渡ります。onProgressUpdate()の引数をLocation型とするために、AsyncTaskの継承のところで<Void, Location, Void>のように真ん中をLocationにしています。

    onProgressUpdate()の中はUIを操作できるスレッドで動くので、TextViewを直接操作できます。コードの例では、2つのTextViewに緯度と経度をそれぞれ出力しています。

    後はコメントで説明を入れているので、そのへんを参照していただければ。

    キャンセル

  • 2017/11/17 09:02

    すみません。自己解決のところに投稿してしまいました。

    キャンセル

  • 2017/11/17 17:22

    気にしない!

    キャンセル

0

何から何までありがとうございます。今無事に動きました!最後に質問なのですが、呼び出しもと(MainActivityの)変数に直接latitudeとlongitudeを代入するには、インスタンスを作ってMainActivity.latitudeとアクセスすればよいのでしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/17 17:15 編集

    ActivityのインスタンスはAndroidフレームワークが作成するもので、ユーザーが勝手に作成してはいけませんし、作成しても意図通りには動きません。稼働しているMainActivityに何か渡したいのであれば、すでに作成されているインスタンスを参照しなければなりません。Asynのコンストラクターで受け取ったContext型の値を用いれば、それを実現することができます。例えば

    public class MainActivity extends AppCompatActivity {
    private double latitude, longitude;
    // 中略

    public void recvLocation(Location location) {
    this.latitude = location.getLatitude();
    this.longitude = location.getLongitude();
    }
    // 以下略

    のようなメソッドを作っておいて、Asyn側の処理では

    ((MainActivity) mContextAsync).recvLocation(mLocation);

    のように呼びだせば渡すことができるでしょう。質問当初のコードでは、AsyncのコンストラクターでgetApplicationContext()を使っていましたが、これを介さず直接受け取っておけば、こうした使い方もできます。

    MainActivityで

    public class MainActivity extends AppCompatActivity {
    public static double latitude, longitude;

    のように宣言していれば、Asyncで
    MainActivity.latitude = mLocation.getLatitude;
    のように渡すことも可能ではありますが、こういう方法は推奨されません(むしろタブーに近い)。

    キャンセル

  • 2017/11/17 17:38 編集

    コンストラクターでContext型を得るクラスはたくさんあります。例えばViewを継承したクラスを自分で作るときとかも

    public MyView extends View {
    Private Context mContext;
    public MyView(Context context) {
    this.mContext = context;
    }

    みたいにして受け取っておけば、このmContextを使って生成元のActivityと情報交換することができます。

    参考にされたStackOverflowの質問でgetApplicationContext()を使った意図はよくわからないのですが、こうしてしまうと上記のような使い方ができなくなってしまいます。Contextというのはアプリケーションやアクティビティの状態管理を司るものなのですが、「アプリケーション全体を指す」ものと「アクティビティごとを指す」ものの2種類があります。利便性を考えれば、保持するのは後者にするべきです。

    最初に回答したrequestPermissionsの処理で、第1引数がエラーになってしまうという件も、直接受け取っておけば

    if (ContextCompat.checkSelfPermission(this.ContextAsync, mPermission[0]) != PackageManager.PERMISSION_GRANTED ||
    ContextCompat.checkSelfPermission(this.ContextAsync, mPermission[1]) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(
    (Activity) this.ContextAsync, mPermission, REQUEST_PERMISSION);
    }

    のようにキャストで解決できたんですよね。getApplicationContext()の結果を保持してしまうと、これができなくなってしまいます。こうしたところでも不便になってしまうんですよね。

    キャンセル

  • 2017/11/17 23:59

    返信遅れてしまってすみません。無事に変数を渡すことができました。このような初心者に付き合っていただきありがとうございます。最後に、contextやpermission等の所謂、書店で売っている開発本等にはあまりのっていないような知識は何処で学習したらよいでしょうか?今回はcontextやActivityに多く苦しめられたので学習したいと思っています。

    キャンセル

  • 2017/11/18 05:24 編集

    表面的な技術だけではなく、Androidの深い部分まで詳しく解説しているサイトとしては、

    Y.A.M の 雑記帳
    http://y-anz-m.blogspot.jp/

    TECHBOOSTER
    https://techbooster.org

    この辺りがお勧めでしょうか。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.20%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる