我有一个非常奇怪的问题,用于更新UI。我有一个前台开始有界服务,我的主要过程在后台。当我启动应用程序时,我想检查服务是否已在运行并更改切换按钮的状态。对于这个问题,我在OnResume()中启动应用程序时绑定到我启动的服务,服务将一个值发送回我的应用程序,该值显示服务的运行状态,并根据此值更新UI。但问题是在这种情况下UI不会更新。
因为这个错误是在非常复杂的情况下显示的,所以我编写了一个重现此问题的示例代码。以下是这些代码(抱歉名字不好,错过了很多错误检查,我已经快速编写了这段代码,只是为了重现问题)。我已经将每个代码作为概述进行了讨论。
activity_main layout
<ToggleButton
android:id="@+id/ui_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="Off State"
android:textOn="On State"
android:checked="false" />
<Button
android:id="@+id/start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"/>
MyTestService.java
首先,这是我的示例前景启动有界服务。如您所见,当我们开始服务时,我们创建一个前台服务,它只运行一个小线程,每隔10秒切换一个mStatus
变量10次,然后停止。每当我们绑定到此服务时,我们都会使用ResultReceiver
通过绑定意图发送,以便将mStatus
发送到应用。我们还允许重新绑定,因为应用程序可能会多次关闭并重新打开。
public class MyTestService extends Service {
private volatile boolean mStatus = false;
private MyThread mTh = new MyThread();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mTh.start();
Intent notintent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notintent, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentText("Test").setContentIntent(pendingIntent).setContentTitle("title").setSmallIcon(R.drawable.ic_launcher);
Notification notification = builder.build();
startForeground(100, notification);
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
ResultReceiver recv = (ResultReceiver)intent.getParcelableExtra("myrecvextra");
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
recv.send(0, data);
}
return null;
}
@Override
public boolean onUnbind(Intent intent) {
return true;
}
@Override
public void onRebind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
ResultReceiver recv = (ResultReceiver)intent.getParcelableExtra("myrecvextra");
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
recv.send(0, data);
}
}
public class MyThread extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++ ) {
Thread.sleep(10000);
mStatus = !mStatus;
Log.i("ASD", String.format("%d", mStatus? 1 : 0));
}
}catch (Exception e) {
}
stopSelf();
}
}
}
MyServiceAccessClass.java
此类用于访问服务。 start()
启动服务,bind()
和unbind()
用于绑定和解除绑定服务。 mRecv
是绑定时发送到服务的ResultReceiver
,用于获取状态。绑定后收到状态时,ResultReceiver
通过回调更新UI。
public class MyServiceAccessClass {
private MyResultRecv mRecv = new MyResultRecv(new Handler(Looper.getMainLooper()));
private OnUpdateRequest mCallback = null;
private Context mCtx = null;
private ServiceConnection mCon = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
public MyServiceAccessClass(Context ctx) {
mCtx = ctx;
mCallback = (OnUpdateRequest)ctx;
}
public void bind() {
Intent intent = new Intent(mCtx, MyTestService.class);
intent.setAction("checkstatus");
intent.putExtra("myrecvextra", mRecv);
mCtx.bindService(intent, mCon, Context.BIND_AUTO_CREATE);
}
public void unbind() {
mCtx.unbindService(mCon);
}
public void start() {
Intent intent = new Intent(mCtx, MyTestService.class);
mCtx.startService(intent);
}
private class MyResultRecv extends ResultReceiver {
public MyResultRecv(Handler handler) {
super(handler);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == 0) {
mCallback.updateUi(resultData.getBoolean("status"));
}
}
}
}
MainActivity.java
这是测试应用程序的主要类。开始按钮开始服务。这个类绑定在OnResume()
中并在OnPause()
中绑定。如果应用在服务已经投放且其mStatus
为true
时运行,则updateUi
将调用true
值,并设置切换按钮的状态。
interface OnUpdateRequest {
public void updateUi(boolean state);
}
public class MainActivity extends AppCompatActivity implements OnUpdateRequest{
private MyServiceAccessClass mTest = new MyServiceAccessClass (this);
private ToggleButton mBtn = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn = (ToggleButton)findViewById(R.id.ui_btn);
((Button)findViewById(R.id.start_btn)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTest.start();
}
});
}
@Override
protected void onResume() {
super.onResume();
mTest.bind();
}
@Override
protected void onPause() {
super.onPause();
mTest.unbind();
}
@Override
public void updateUi(boolean state) {
mBtn.setChecked(state);
}
}
好的,现在理论上一切都很好。但是,如果您尝试使用此代码,当服务启动且mStatus
为真时,将使用setChecked()
调用切换按钮true
(到目前为止这是正确的),但UI不会已更新以显示正确的文本和状态。有趣的是,如果您为此切换按钮运行isChecked
,它将返回true
,但UI会显示其他内容。
知道为什么会这样吗?很抱歉,很多代码出现了这个问题。
更新
我注意到了我应该提到的一些事情。如果我在isChecked
之后立即使用setCheck
,我会true
这是正确的。但是如果我稍后再次使用isChecked
(例如在另一个按钮事件处理程序中),它将返回false
,而我还没有调用setChecked
。我认为这种情况与我的问题有关,但我不知道这是怎么回事。
此外,我认为此问题与您在绑定服务的过程中更新UI有关。因为如果我在绑定过程中尝试使用相同的ResultReceiver
更新应用主界面,那么一切正常。
答案 0 :(得分:0)
可能需要在按钮上调用View.requestLayout()
或View.forceLayout()
来刷新按钮状态。
答案 1 :(得分:0)
我终于发现了我的代码问题。 我花了很多时间来解决这个问题,所以我在这里发布给其他Android开发者。
通过ResultReceiver
以某种方式显而易见地从服务发回结果。但是互联网上的大多数示例都没有显示服务重新绑定,而我从未发现重新绑定服务后发回结果。
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
ResultReceiver recv = (ResultReceiver)intent.getParcelableExtra("myrecvextra");
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
recv.send(0, data);
}
return null;
}
@Override
public boolean onUnbind(Intent intent) {
return true;
}
@Override
public void onRebind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
ResultReceiver recv = (ResultReceiver)intent.getParcelableExtra("myrecvextra");
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
recv.send(0, data);
}
}
这是一种制作重新绑定服务的常用方法,它基于您在Internet中找到的对服务的简单绑定。这已经在true
中使用onUnbind()
返回onRebind()
并使用ResultReceiver
完成了。但这种方法完全错误。
为什么呢?因为android中的一个奇怪的设计。在Service OnRebind()中,有一个18字的小评论:
请注意此时Intent附带的任何额外内容 将不会在这里看到。
现在这意味着什么?这意味着带有ResultReceiver
的额外值将在重新绑定时不可用,这反过来意味着重新绑定后不会发回结果。但由于未知原因,此代码不会出现任何异常,您甚至会在调试时在应用程序中看到结果,因此,为什么此代码不起作用,这是非常模糊的。
现在解决方案是什么?绑定到bindService()
意图的服务时,永远不要发送ResultReceiver
。尽管这对于非重新绑定服务是正确的,但我强烈建议避免使用它。在调用onServiceConnected
时,通过单独的消息发送public static int SERVICE_SET_RECV = 1;
public static String SERVICE_RECV = "SERVICE_RECV";
private Messenger mMessenger = new Messenger(new MyHandler(this));
private ResultReceiver mRecv = null;
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
return mMessenger.getBinder();
}
return null;
}
@Override
public boolean onUnbind(Intent intent) {
mRecv = null;
return true;
}
@Override
public void onRebind(Intent intent) {}
public void setRecv(ResultReceiver recv) {
mRecv = recv;
// Example to send some result back to app
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
mRecv.send(0, data);
}
private static class MyHandler extends Handler {
private final WeakReference<MyTestService> mService;
public MyHandler(MyTestService service) {
mService = new WeakReference<>(service);
}
@Override
public void handleMessage(Message msg) {
MyTestService service = mService.get();
Bundle data = msg.getData();
switch (msg.what) {
case SERVICE_SET_RECV: {
ResultReceiver recv = data.getParcelable(SERVICE_RECV);
service.setRecv(recv);
break;
}
default:
super.handleMessage(msg);
}
}
}
到服务,然后一切都像小菜一样。以下是我对代码的修改:
<强> MyTestService.java 强>
private ServiceConnection mCon = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Messenger messenger = new Messenger(service);
Bundle data = new Bundle();
data.putParcelable(MyTestService.SERVICE_RECV, mRecv);
Message msg = Message.obtain(null, MyTestService.SERVICE_SET_RECV, 0, 0);
msg.setData(data);
messenger.send(msg);
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
public void bind() {
Intent intent = new Intent(mCtx, MyTestService.class);
mCtx.bindService(intent, mCon, Context.BIND_AUTO_CREATE);
}
<强> MyServiceAccessClass.java 强>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="MYACTIVITY"
android:id="@+id/activity_MY"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<Button
android:text="Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:id="@+id/button"/>
<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:id="@+id/progressBar2"
android:layout_above="@+id/button"
android:layout_height="50dp"
android:max="100"
android:progress="50" />
</RelativeLayout>
找到这个荒谬的问题花了我很多时间。我希望每个人都喜欢这个解决方案,并为重新绑定服务解决了很多问题。