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

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

新規登録して質問してみよう
ただいま回答率
85.51%
Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Q&A

解決済

2回答

14258閲覧

他クラスからTextViewを変更したい

K-Tomoya

総合スコア11

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

0グッド

1クリップ

投稿2017/10/04 02:14

###前提・実現したいこと
Androidでアプリ開発をしています。
ServiceからActivityのTextViewに文字列を渡す機能を実装しようとしているのですが
以下のエラーメッセージが発生しました。

センサーが値を取得した時に呼ばれるonSensorChangedの中でサーバーとHTTP通信をするようのスレッドを立てるのですが、そのスレッドの中でMainActivityのテキストビューに文字列を代入しようとしています。

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

FATAL EXCEPTION: main Process: com.example.kimuratomoya.sensor_db_backup, PID: 14643 java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View com.example.kimuratomoya.sensor_db_backup.MainActivity.findViewById(int)' on a null object reference at com.example.kimuratomoya.sensor_db_backup.SendDataService.ChangeTextView(SendDataService.java:88) at com.example.kimuratomoya.sensor_db_backup.SendDataService$3$2.run(SendDataService.java:601) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5253) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:900) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:695)

###該当のソースコード

public class MainActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); 〜中略〜 //成功したHTTPリクエスト数と失敗したHTTPリクエスト数のカウント new SendDataService(this); 〜中略〜
public class SendDataService extends Service implements SensorEventListener, LocationListener { private static MainActivity mainActivity; private Context context = this; private Context activitycon; public int errornum=0,sucnum=0; public TextView success_text, error_text; 〜中略〜 //引数のないコンストラクタを作らないといけない。 public SendDataService(){ } //MainActivity.javaから受け取ったContextを変数に代入するための関数 public SendDataService(Context con){ activitycon = con; } //テキストビューを変更する関数 public void ChangeTextView(Context con, Integer sucstr, Integer erstr){ //Context testcon = mainActivity.getBaseContext(); success_text = (TextView)((com.example.kimuratomoya.sensor_db_backup.MainActivity) activitycon).findViewById(R.id.success_id); success_text.setText(sucstr); error_text = (TextView)((com.example.kimuratomoya.sensor_db_backup.MainActivity) activitycon).findViewById(R.id.error_id); error_text.setText(erstr); } 〜中略〜 public int onStartCommand(Intent intent, int flags, int startId) { 〜中略〜 } @Override public void onSensorChanged(SensorEvent event) { 〜中略〜 Log.d("sensor", "スレッド立ち上げ"); // 通信用のスレッドを起動 new Thread(new Runnable() { @Override public void run() {   try { 〜中略〜 //テキストビューを変更する関数を呼び出しているところ sucnum += 1; ChangeTextView(activitycon, sucnum, errornum); } catch (Exception e) { try {     //テキストビューを変更する関数を呼び出しているところ errornum += 1; ChangeTextView(activitycon, sucnum, errornum); if (conn != null) conn.disconnect(); } 〜中略〜 }).start(); } 〜中略〜 }

###試したこと
public SendDataService(Context con)にMainActivityから直接コンテキストを渡せばテキストビューを変更できるのですが、Service内の他の関数(onSensorChanged)などからも、テキストビューを変更したくて、このようなプログラムになっています。
AplicationContextやActivityContextの違いなども調べたのですが、具体的にどのようにすればいいのかが、わかりません。
ChangeTextView()の中でgetAplicationContextを使ってコンテキストを取得しようとしたりしたのですが、同じエラーが出てしまいました。

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

t_obara

2017/10/04 03:17

全体を読み切れておりませんが、intentなどでテキストのみを渡し、TextViewを保有しているアプリがそれをハンドリングしてTextViewに反映させるという方法では問題があるのでしょうか?
K-Tomoya

2017/10/10 11:37

それで大丈夫です!今はそのやり方で行き詰まっていて、サービスからstarActivityをしたら、MainActivityのonCreateの中のstartServiceが実行されて、アクティビティとサービスが交互に高速で起動するという状態になってしまっています。どうしたらいいでしょうか?
guest

回答2

0

コードがいろいろと省略されているため憶測ですが、
システムがstartServiceメソッドの呼び出しを受けて生成したインスタンスと、
あなたがnewしたことによって生成されたインスタンスの2つ生成されていると考えられます。

サービスとして動いているのはシステムが生成したほうで、
インスタンスの生成にはコンストラクタの引数がないものが使われているはずです。
そのためactivityconはnullのままで、activityconを操作する段階で落ちます。

あなたがnewしたほうは参照の保持すらされていないので
早々にGCで回収されていることでしょう。

投稿2017/10/04 03:56

abs123

総合スコア1280

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

K-Tomoya

2017/10/10 11:26

なるほど!そもそもactivitycon = con;が実行されていなかったのですね。 コメントありがとうございます
guest

0

ベストアンサー

他のスレッドからTextViewの更新をしている為にエラーが起きています。

TextViewは、この場合MainActivityが動作しているUIスレッドで更新する必要があります。

「Android UIスレッド 更新」等でググるとたくさんの情報にヒットしますので見てみてください。
具体的には、
Handler#post(Runnnable)とか、
TextView#post(Runnable)とか、
Activity#runOnUiThread(Runnable) 等の
メソッドを利用して、その中でTextViewを更新することになると思います。

※上記、外している可能性があるので追記させていただきます。
ご提示のコードをよく見ると
public void onSensorChanged() 内で
new Thread(new Runnable() {
@Override
public void run() {
としているので、一見、大丈夫なようにも見えます。

問題無いかどうかは、SensorEventListenerが違うスレッドで動作していて、
OnSensorChanged()メソッドがそのスレッドで呼ばれているかどうかによります。

###ActivityとService間の通信について追記(2017/10/11)
ご質問の問題の中身とコメントの内容を再考してみますと、どうも混乱させてしまったようですので、追記することで整理させていただきたいと思います。※混乱のひとつは私の回答のせいであります。

当初の質問で示された以下のエラーに関してですが、

java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View com.example.kimuratomoya.sensor_db_backup.MainActivity.findViewById(int)' on a null object reference

この点の回答としては、abs123様のものが適切だと考えています。(K-Tomoya様、これはOKですよね?)で、次に、私が最初に回答させてもらった件

他のスレッドからTextViewの更新をしている為にエラーが起きています。
TextViewは、この場合MainActivityが動作しているUIスレッドで更新する必要があります。

ですが、これはこれでその通りなのですが、今回はActivityとService間の通信の話になってくるので、ActivityのUIスレッドだけの話では終わらないです。この点、私の回答が拙速であったこと、お詫びします。

では結局、Activityが持っているTextViewをService側で発生したデータで更新して表示したいと言う要求はどうしたらいいのかと言うと、簡単には「ActivityにBroadcastReceiverを持たせ、Serivice側からActivityへIntentで通知する。」との方法が適切かと思います。その為のサンプルを示します。Service内で1秒毎にActivityへIntentを送信し、ActivityではそのIntentから取り出した文字列をTextViewに表示するだけのものです。短いので全部載せます。

尚、本サンプルではActivityが停まってもServiceは停まりませんのでご注意ください。

Activityの、MainActivity.java です。

Java

1package examples.products.test24; 2 3import android.content.BroadcastReceiver; 4import android.content.Context; 5import android.content.Intent; 6import android.content.IntentFilter; 7import android.support.v7.app.AppCompatActivity; 8import android.os.Bundle; 9import android.util.Log; 10import android.widget.TextView; 11 12public class MainActivity extends AppCompatActivity { 13 14 private static final String TAG = "Test24.MainActivity"; 15 16 private BroadcastReceiver mReceiver = null; 17 private IntentFilter mIntentFilter = null; 18 private TextView mTextView1 = null; 19 20 @Override 21 protected void onCreate(Bundle savedInstanceState) { 22 super.onCreate(savedInstanceState); 23 setContentView(R.layout.activity_main); 24 mTextView1 = (TextView) findViewById(R.id.textview1); 25 26 mReceiver = new BroadcastReceiver() { 27 @Override 28 public void onReceive(Context context, Intent intent) { 29 // このonReceiveでMainServiceからのIntentを受信する。 30 Bundle bundle = intent.getExtras(); 31 String message = bundle.getString("message"); 32 Log.d(TAG, "Message from MainService: " + message); 33 // TextViewへ文字列をセット 34 mTextView1.setText(message); 35 } 36 }; 37 38 // "TEST24_ACTION" Intentフィルターをセット 39 mIntentFilter = new IntentFilter(); 40 mIntentFilter.addAction("TEST24_ACTION"); 41 registerReceiver(mReceiver, mIntentFilter); 42 43 Intent intent = new Intent(getApplication(), MainService.class); 44 startService(intent); 45 46 // !!!! サンプルの為、MainServiceサービスを停める処理が無いので、注意してください。 47 } 48}

Serviceの、MainService.javaです。

Java

1package examples.products.test24; 2 3import android.app.Service; 4import android.content.Intent; 5import android.os.IBinder; 6import android.util.Log; 7 8import java.util.Timer; 9import java.util.TimerTask; 10 11public class MainService extends Service { 12 private static final String TAG = "Test24.MainService"; 13 private Timer mTimer = null; 14 private int mCount = 0; 15 16 @Override 17 public void onCreate() { 18 super.onCreate(); 19 } 20 21 @Override 22 public int onStartCommand(Intent intent, int flags, int startId) { 23 Log.d(TAG, "onStartCommand():"); 24 mTimer = new Timer(); 25 mTimer.schedule(new TimerTask() { 26 @Override 27 public void run() { 28 mCount++; 29 30 // MainActivityへデータを送信 31 String message = String.format("count=%d", mCount); 32 sendBroadcast(message); 33 } 34 }, 0, 1000); 35 36 return super.onStartCommand(intent, flags, startId); 37 } 38 39 @Override 40 public void onDestroy() { 41 super.onDestroy(); 42 Log.d(TAG, "onDestroy():"); 43 // タイマーを停止 44 if (mTimer != null) { 45 mTimer.cancel(); 46 mTimer = null; 47 } 48 } 49 50 @Override 51 public IBinder onBind(Intent intent) { 52 Log.d(TAG, "onBind():"); 53 return null; 54 } 55 56 private void sendBroadcast(String message) { 57 Log.d(TAG, "sendBroadcast: " + message); 58 59 // IntentをブロードキャストすることでMainActivityへデータを送信 60 Intent intent = new Intent(); 61 intent.setAction("TEST24_ACTION"); 62 intent.putExtra("message", message); 63 getBaseContext().sendBroadcast(intent); 64 } 65}

AndroidManifest.xmlです

XML

1<?xml version="1.0" encoding="utf-8"?> 2<manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="examples.products.test24"> 4 5 <application 6 android:allowBackup="true" 7 android:icon="@mipmap/ic_launcher" 8 android:label="@string/app_name" 9 android:roundIcon="@mipmap/ic_launcher_round" 10 android:supportsRtl="true" 11 android:theme="@style/AppTheme"> 12 <activity android:name=".MainActivity"> 13 <intent-filter> 14 <action android:name="android.intent.action.MAIN" /> 15 16 <category android:name="android.intent.category.LAUNCHER" /> 17 </intent-filter> 18 </activity> 19 20 <service android:name=".MainService"></service> 21 </application> 22</manifest>

レイアウトのlayout/activity_main.xmlです。

XML

1<?xml version="1.0" encoding="utf-8"?> 2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:orientation="vertical" 8 tools:context="examples.products.test24.MainActivity"> 9 10 <TextView 11 android:id="@+id/textview1" 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:text="Text#1" 15 app:layout_constraintBottom_toBottomOf="parent" 16 app:layout_constraintLeft_toLeftOf="parent" 17 app:layout_constraintRight_toRightOf="parent" 18 app:layout_constraintTop_toTopOf="parent" /> 19</LinearLayout>

UIスレッドではないスレッドからのTextView等のビューの更新についてですが、先の回答で提示させてもらった以下のクラスメソッドの利用

  • Handler#post(Runnnable)
  • TextView#post(Runnable)
  • Activity#runOnUiThread(Runnable)

は、これらはActivityで SensorEventListener, LocationListener等を使っていて、イベントがActivityとは違うスレッドで通知される場合に必要になります。今回はServiceでイベントをハンドリングしているので、恐らく問題にはならないと思われます。混乱させてすみません。

投稿2017/10/04 03:34

編集2017/10/11 06:40
dodox86

総合スコア9181

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

K-Tomoya

2017/10/10 11:32

完璧に理解しきれていないので、もしかしたら間違った解釈をしているかもしれませんが、 Handler#post(Runnnable)、Activity#runOnUiThread(Runnable)は試した時にうまくいかなくて、 うまくいかなかった理由が、Activityを持っているクラス(MainActivity)とHandlerを持っているクラス(SendDataService)が違うので、Handlerが使えなかった。UIスレッドは、サービスからはアクセスできないため。 というように解釈し、この方法では無理なのかなと思っていました。 この考え方は間違っているでしょうか?
dodox86

2017/10/11 02:06

もともとのご質問の回答/Exceptionの原因としてはabs123様のものが適切だったと思いますが、 Handlerに関してうまくいかなかったのは、そのご理解でおおむね正しいかと思います。 正確を期すると、クラスが違う為と言うよりは「Handlerのインスタンスを生成したスレッド」で run部分が実行される為、MainActivityのスレッドでChangeTextView()が実行されるようにしなければ 表示されない、とのようになるかと。こちらで書かれている記事が分かり易いかと思いますので、ご紹介させていただきます。 http://d.hatena.ne.jp/sankumee/20120329/1333021847
K-Tomoya

2017/10/11 03:41 編集

記事まで探していただいて、ありがとうございます!この記事は僕も前に見たのですが、分からないところが幾つかあります。 記事でいうHogeTaskクラスは僕でいうMainActivityクラスなのでしょうか? つまりpublic void execute(Looper toLooper, String data) をMainActivityに書けばいいのでしょうか?そうなると、Looperなど渡さなくても、Stringだけでテキストビューは変えられると思います。 あとThreadは継承しないといけないのでしょうか? そうではなく、Service側にpublic void execute(Looper toLooper, String data) を用意するとしたら、 public void run() {の中にsuccess_text.setText();を書かなければならず、 TextView success_text = (TextView)findViewById(R.id.success_id);を書いたら、 findViewByIdの所に「cannot resolve method findViewById(int)」というエラーが出てしましました。 stackOverflowにonCreate()に書けと書いてるのですが、サービスの中にonCreate()を作るべきですか? onStartCommand()の中に書いても、同じエラーでした。 手取り足取り教えて頂くようで申し訳ないのですが、よろしくお願い致します。
dodox86

2017/10/11 07:00

(私の回答が混乱を招いた感もありましたので追記しました。そちらもご覧ください) HogeTaskはただのクラスなので、MainActivityかと言うと微妙かも。 むしろ、new HogeTask().execute(toLooper, "hogehage"); をする呼び出し側が MainActivityな気もします。 クラスとスレッドは別に考えないと混乱します。スレッドAがクラスAをnewしてメソッドを呼び出せばそのクラスAのインスタンスはスレッドAで動いていますし、スレッドBがクラスAをnewしてメソッドを呼び出せば、そのインスタンスはスレッドBで動いているコードです。 Service側のコードでMainActivityのインスタンスメソッドfindTextByIdを呼ぶのは無理があります。Serviceの中でMainActivity.onCreate()は呼べない、と言うかonCreateはAndroidのシステムが呼び出してくるものです(コールバック) Serviceの中で自作のCreateTextView()は呼べない、と思ってください。Android内部を深く理解していれば方法はあるかもしれませんが、少しハックめいている気がするのでお勧めできないです。
K-Tomoya

2017/11/09 06:10

返信が大変遅くなり申し訳ありません。他の課題で詰まっていたため、アクティビティに取り掛かる時間が取れずに遅くなってしまいました。 dodox86さんのおかげでアクティビティについて少しは理解することができました!ありがとうございました!
dodox86

2017/11/09 06:29

コメントいただきどうもありがとうざいます。少しでも役に立ってよかったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.51%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問