我试图学习如何使用绑定服务。每当我单击“开始/暂停”按钮时,我希望计时器根据需要进行更改。但是,单击这些按钮将终止该程序。我不确定为什么这个TextView对象在我的服务类中给出了NullPointerException错误。有人可以帮帮我吗?
public class LocalService extends Service {
//Binder
private final IBinder mBinder = new LocalBinder();
Button btnStart,btnPause,btnLap;
TextView txtTimer;
Handler customHandler = new Handler();
LinearLayout container;
long startTime=0L,timeInMilliseconds=0L,timeSwapBuff=0L,updateTime=0L;
public class LocalBinder extends Binder {
LocalService getService() {
//Return this instance of LocalService so public methods can be called
return LocalService.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
Runnable updateTimerThread = new Runnable() {
@Override
public void run() {
timeInMilliseconds = SystemClock.uptimeMillis()-startTime;
updateTime = timeSwapBuff+timeInMilliseconds;
int secs=(int)(updateTime/1000);
int mins=secs/60;
secs%=60;
int milliseconds=(int)(updateTime%1000);
txtTimer.setText(""+mins+":"+String.format("%02d",secs)+":"
+String.format("%03d",milliseconds));
customHandler.postDelayed(this,0);
}
};
}
此对象已在我的Main类中初始化:
public class MainActivity extends AppCompatActivity {
LocalService mService = new LocalService();
boolean mBound = false;
Button btnStart, btnPause, btnLap;
TextView txtTimer;
LinearLayout container;
Handler customHandler = new Handler();
long startTime=0L,timeInMilliseconds=0L,timeSwapBuff=0L,updateTime=0L;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStart = (Button)findViewById(R.id.btnStart);
btnPause = (Button)findViewById(R.id.btnPause);
btnLap = (Button)findViewById(R.id.btnLap);
txtTimer = (TextView)findViewById(R.id.timerValue);
container = (LinearLayout)findViewById(R.id.container);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startTime = SystemClock.uptimeMillis();
customHandler.postDelayed(mService.updateTimerThread,0);
}
});
btnPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
timeSwapBuff=+timeInMilliseconds;
customHandler.removeCallbacks(mService.updateTimerThread);
}
});
btnLap.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
LayoutInflater inflater = (LayoutInflater)getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View addView = inflater.inflate(R.layout.row,(ViewGroup)null);
TextView txtValue = (TextView)addView.findViewById(R.id.textContent);
txtValue.setText(txtTimer.getText());
container.addView(addView);
}
});
}
@Override
protected void onStart() {
super.onStart();
//Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(mConnection);
mBound = false;
}
//Define callbacks for service binding, passed to bindService()
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName mainActivity, IBinder service) {
//We've bound to LocalService, cast the IBinder and get LocalService instance
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
}
当此服务运行时,我希望使用绑定的服务值更新主活动中的txtTimer。我得到一个NullPointer错误代码,将我带到第56行附近的LocalService类:
txtTimer.setText(""+mins+":"+String.format("%02d",secs)+":"
+String.format("%03d",milliseconds));
更新这个的最佳方法是什么,以便txtTimer能够显示变化的值?我真的很感激一些帮助
答案 0 :(得分:3)
在LocalService
class:
public LocalService(TextView textView){
txtTimer = textView;
super();
}
并在btnStart.setOnClickListener(new View ...
之前添加此行:
mService = new LocalService(txtTimer);
希望它会有所帮助
答案 1 :(得分:1)
问题是您的txtTimer
对象永远不会在您的服务中初始化。修改服务中的UI元素很棘手,最好不要直接从服务本身完成。
通常,解决方案是实现BroadcastReceiver
。您只需将您想要的任何数据(在本例中为字符串)发送到您的活动,然后该活动将接收该数据。
首先,在您的服务中,您创建一个Intent
对象,在其中您将Bundle
包含您要发送到活动的任何数据:
Runnable updateTimerThread = new Runnable() {
@Override
public void run() {
timeInMilliseconds = SystemClock.uptimeMillis()-startTime;
updateTime = timeSwapBuff+timeInMilliseconds;
int secs=(int)(updateTime/1000);
int mins=secs/60;
secs%=60;
int milliseconds=(int)(updateTime%1000);
// Get the data to send
String string = ""+mins+":"+String.format("%02d",secs)+":"
+String.format("%03d",milliseconds);
Intent intent = new Intent("MainActivityListener");
Bundle bundle = new Bundle();
bundle.putString("txtTimerString", string);
intent.putExtras(bundle);
// Send broadcast
getApplicationContext().sendBroadcast(intent);
customHandler.postDelayed(this,0);
}
};
然后在您的活动中,您需要创建BroadcastReciever
,它将从服务中接收您想要的字符串并相应地设置TextView:
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
String txtTimerString = extras.getString("txtTimerString");
if (txtTimerString != null) {
txtTimer.setText(txtTimerString);
}
}
}
};
最后,您需要在活动的BroadcastReciever
方法中注册onResume
:
@Override
public void onResume() {
super.onResume();
getApplicationContext().registerReceiver(mMessageReceiver,
new IntentFilter("MainActivityListener"));
}
这比尝试找到将TextView直接传递到服务的方法要安全得多,因为服务可以独立于活动运行。如果服务尝试修改TextView并且活动当前未运行,则可能会崩溃。这样,服务就会发出一个广播 - 如果活动没有运行,那就无所谓了。