从服务更新Android异步UI失败
我在更新UI时遇到了一个非常奇怪的问题。我有一个前台启动的有界服务,我的主要进程在后台。当我启动应用程序时,我喜欢检查服务是否已经在运行,并更改切换按钮的状态。对于这个问题,在OnResume()中启动应用程序时,我绑定到我启动的服务,服务将一个值发送回我的应用程序,该值显示服务的运行状态,我根据该值更新UI。但问题是,在这种情况下,UI不会更新 因为这个bug是在非常复杂的情况下出现的,所以我编写了一个示例代码来重现这个问题。下面是这些代码(很抱歉名称不好,并且错过了很多错误检查,我很快编写了这些代码只是为了重现这个问题)。作为一个概述,我已经对每段代码进行了一些讨论 活动\u主布局:从服务更新Android异步UI失败,android,android-layout,Android,Android Layout,我在更新UI时遇到了一个非常奇怪的问题。我有一个前台启动的有界服务,我的主要进程在后台。当我启动应用程序时,我喜欢检查服务是否已经在运行,并更改切换按钮的状态。对于这个问题,在OnResume()中启动应用程序时,我绑定到我启动的服务,服务将一个值发送回我的应用程序,该值显示服务的运行状态,我根据该值更新UI。但问题是,在这种情况下,UI不会更新 因为这个bug是在非常复杂的情况下出现的,所以我编写了一个示例代码来重现这个问题。下面是这些代码(很抱歉名称不好,并且错过了很多错误检查,我很快编写了
<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"/>
MyServiceAccessClass.java
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);
}
}
}
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);
}
此类用于访问服务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
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);
}
}
}
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);
}
这是测试应用程序的主要类。启动按钮启动服务。这个类在OnResume()
中绑定,在OnPause()中解除绑定。如果应用程序在服务已经运行时运行,并且其mStatus
为true
,则将使用true
值调用updateUi
,并设置切换按钮的状态
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
为true时,将使用true
调用切换按钮的setChecked()。有趣的是,如果您运行此切换按钮的isChecked
,它将返回true
,但UI显示其他内容
知道为什么会这样吗?很抱歉出现了很多代码,出现的这个问题就是这种复杂的情况
更新
我注意到一些我应该提及的事情。如果在setCheck
之后立即使用isChecked
,我会得到正确的true
。但是,如果我稍后再次使用isChecked
(例如在另一个按钮事件处理程序中),它将返回false
,而我不再调用setChecked
。我认为这种情况与我的问题有关,但我不知道这是怎么发生的
此外,我认为这个问题与在绑定到服务的过程中更新UI有关。因为如果我不在绑定过程中,尝试使用相同的ResultReceiver
更新应用程序主界面,一切都正常。可能需要调用按钮视图上的视图。requestLayout()
或视图。forceLayout()
,以刷新按钮状态。我终于发现了代码的问题解决这个问题花了我很多时间,所以我在这里发布给其他android开发者
通过ResultReceiver
,从服务返回结果在某种程度上是显而易见的。但internet上的大多数示例都没有显示服务重新绑定,而且我从未发现在重新绑定服务后返回结果
好的,现在有什么问题?查看“我的服务”中的以下代码部分:
@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中找到的服务的简单绑定。这是通过在onUnbind()
中返回true
并使用onRebind()
完成的。但这种方法是完全错误的
为什么??因为android的一个奇怪的设计。在中,有一个18字的小评论:
请注意,在该点上包含的任何附加内容都是出于意图
在这里看不到
这意味着什么?这意味着在重新绑定时,携带ResultReceiver
的额外数据将不可用,这反过来意味着在重新绑定后结果将不会返回。但由于未知的原因,这段代码并没有出现任何异常,你们甚至在调试时可以在应用程序中看到结果,所以这段代码不工作的原因非常模糊
现在解决办法是什么?使用bindService()
intent绑定到服务时,切勿发送ResultReceiver
。尽管这对于非重新绑定服务是正确的,但我强烈建议避免它。调用服务连接上的onServiceConnected
时,通过单独的消息将ResultReceiver
发送到服务,然后一切都像小菜一碟一样工作。以下是我对代码的修改:
MyTestService.java
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);
}
}
}
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);
}
发现这个可笑的问题花了我很多时间。我希望每个人都喜欢这个解决方案,并解决了许多重新绑定服务的问题。我尝试调用invalidate()
,但没有成功。我也会尝试这两个。无论是requestLayout()
还是forceLayout()
都不起作用。你能试着把runOnUiThread()
放在mCallback.updateUi(resultData.getBoolean(“status”)代码>只是一个想法…不起作用。顺便说一句,我对主要问题做了一个小的更新。我认为您的绑定过程问题是因为这一行:intent.putExtra(“myrecvextra”,mRecv)代码>在这里,您将获得此对象的副本(因为parcelable)。我认为回调也将被复制,因此您将更新副本的togglebutton。也许此链接可以帮助您: