我有一個更新UI的非常奇怪的問題。我有一個前臺開始有限的服務,我的主要過程在後臺。當我啓動應用程序時,我喜歡檢查服務是否已在運行並更改切換按鈕的狀態。對於這個問題,當在OnResume()中啓動應用程序時,我將綁定到我的啓動服務,並且服務將值發送回顯示服務運行狀態的應用程序,並根據此值更新UI。但問題是在這種情況下UI不會更新。Android異步用戶界面從服務更新失敗
由於此錯誤顯示在一個非常複雜的情況下,我寫了一個示例代碼來重現此問題。這裏是這些代碼(對於錯誤的名字抱歉,錯過了很多錯誤檢查,我已經很快寫了這些代碼來重現問題)。我已經討論過每個代碼作爲概述。
activity_main佈局:
<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
爲true時,切換按鈕的setChecked()
將與true
一起調用(直到現在這是正確的),但UI將不會更新以顯示正確的文本和狀態。有趣的是,如果您爲此切換按鈕運行isChecked
,它將返回true
,但UI會顯示其他內容。
任何想法爲什麼發生這種情況?對不起,很多代碼,這個問題發生在這種複雜的情況。
更新
我注意到的東西,我應該不在話下。如果我在setCheck
之後使用isChecked
,我會得到true
,這是正確的。但是如果我稍後再次使用isChecked
(例如在另一個按鈕事件處理程序中),它將返回false
,而我還沒有再調用setChecked
。我認爲這種情況與我的問題有關,但我不知道這是怎麼發生的。
此外,我認爲這個問題與更新用戶界面時有關,當你在綁定過程中的服務。因爲如果我不在綁定過程中嘗試使用相同的ResultReceiver
更新應用主界面,則一切正常。
我試着調用'invalidate()',但沒有奏效。我也會嘗試這兩個。 – Afshin
'requestLayout()'和'forceLayout()'都不能工作。 – Afshin
你可以嘗試在'mCallback.updateUi(resultData.getBoolean(「status」))周圍放置'runOnUiThread()';'只是一個想法... – beeb