actionbar教學 來自服務的Android更新活動UI




android actionbar隱藏 (6)

我有一個服務,一直在檢查新的任務。 如果有新任務,我想刷新活動UI以顯示該信息。 我確實找到了https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/這個例子。 這是一個很好的接近? 任何其他的例子?

謝謝。


Answer #1

請參閱下面的我的原始答案 - 該模式運行良好,但最近我開始使用不同的方法進行服務/活動通信:

  • 使用綁定服務 ,使活動獲得對服務的直接引用,從而允許直接調用它,而不是使用Intents。
  • 使用RxJava執行異步操作。

  • 如果即使沒有活動正在運行,服務需要繼續後台操作,也要從Application類啟動服務,以便在未綁定時不會停止。

與startService()/ LocalBroadcast技術相比,我在這種方法中找到的優點是

  • 不需要數據對象來實現Parcelable - 這對我來說特別重要,因為我現在在Android和iOS之間共享代碼(使用RoboVM)
  • RxJava提供了罐裝(和跨平台)調度,以及順序異步操作的組合。
  • 這應該比使用LocalBroadcast更高效,儘管使用RxJava的開銷可能會超過這個。

一些示例代碼。 首先服務:

public class AndroidBmService extends Service implements BmService {

    private static final int PRESSURE_RATE = 500000;   // microseconds between pressure updates
    private SensorManager sensorManager;
    private SensorEventListener pressureListener;
    private ObservableEmitter<Float> pressureObserver;
    private Observable<Float> pressureObservable;

    public class LocalBinder extends Binder {
        public AndroidBmService getService() {
            return AndroidBmService.this;
        }
    }

    private IBinder binder = new LocalBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        logMsg("Service bound");
        return binder;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_NOT_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
        if(pressureSensor != null)
            sensorManager.registerListener(pressureListener = new SensorEventListener() {
                @Override
                public void onSensorChanged(SensorEvent event) {
                    if(pressureObserver != null) {
                        float lastPressure = event.values[0];
                        float lastPressureAltitude = (float)((1 - Math.pow(lastPressure / 1013.25, 0.190284)) * 145366.45);
                        pressureObserver.onNext(lastPressureAltitude);
                    }
                }

                @Override
                public void onAccuracyChanged(Sensor sensor, int accuracy) {

                }
            }, pressureSensor, PRESSURE_RATE);
    }

    @Override
    public Observable<Float> observePressure() {
        if(pressureObservable == null) {
            pressureObservable = Observable.create(emitter -> pressureObserver = emitter);
            pressureObservable = pressureObservable.share();
        }
         return pressureObservable;
    }

    @Override
    public void onDestroy() {
        if(pressureListener != null)
            sensorManager.unregisterListener(pressureListener);
    }
} 

和綁定到服務並接收壓力高度更新的活動:

public class TestActivity extends AppCompatActivity {

    private ContentTestBinding binding;
    private ServiceConnection serviceConnection;
    private AndroidBmService service;
    private Disposable disposable;

    @Override
    protected void onDestroy() {
        if(disposable != null)
            disposable.dispose();
        unbindService(serviceConnection);
        super.onDestroy();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.content_test);
        serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                logMsg("BlueMAX service bound");
                service = ((AndroidBmService.LocalBinder)iBinder).getService();
                disposable = service.observePressure()
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(altitude ->
                        binding.altitude.setText(
                            String.format(Locale.US,
                                "Pressure Altitude %d feet",
                                altitude.intValue())));
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                logMsg("Service disconnected");
            }
        };
        bindService(new Intent(
            this, AndroidBmService.class),
            serviceConnection, BIND_AUTO_CREATE);
    }
}

此活動的佈局是:

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.controlj.mfgtest.TestActivity">

        <TextView
            tools:text="Pressure"
            android:id="@+id/altitude"
            android:gravity="center_horizontal"
            android:layout_gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>
</layout>

如果服務需要在沒有綁定Activity的情況下在後台運行,則可以使用Context#startService()從Application類以及OnCreate()啟動該服務。

我的原始答案(從2013年起):

在你的服務中:(使用COPA作為服務在下面的例子中)。

使用LocalBroadCastManager。 在您的服務的onCreate,設置廣播公司:

broadcaster = LocalBroadcastManager.getInstance(this);

當你想通知用戶界面的東西:

static final public String COPA_RESULT = "com.controlj.copame.backend.COPAService.REQUEST_PROCESSED";

static final public String COPA_MESSAGE = "com.controlj.copame.backend.COPAService.COPA_MSG";

public void sendResult(String message) {
    Intent intent = new Intent(COPA_RESULT);
    if(message != null)
        intent.putExtra(COPA_MESSAGE, message);
    broadcaster.sendBroadcast(intent);
}

在你的活動中:

在onCreate上創建一個監聽器:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.setContentView(R.layout.copa);
    receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String s = intent.getStringExtra(COPAService.COPA_MESSAGE);
            // do something here.
        }
    };
}

並在onStart中註冊它:

@Override
protected void onStart() {
    super.onStart();
    LocalBroadcastManager.getInstance(this).registerReceiver((receiver), 
        new IntentFilter(COPAService.COPA_RESULT)
    );
}

@Override
protected void onStop() {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
    super.onStop();
}

Answer #2

我將使用綁定服務來實現這一點,並通過在我的活動中實現一個監聽器來與之通信。 因此,如果您的應用實現了myServiceListener,那麼您可以在綁定它之後將其註冊為服務器中的偵聽器,從綁定的服務調用listener.onUpdateUI並更新您的UI!


Answer #3
Callback from service to activity to update UI.
ResultReceiver receiver = new ResultReceiver(new Handler()) {
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        //process results or update UI
    }
}

Intent instructionServiceIntent = new Intent(context, InstructionService.class);
instructionServiceIntent.putExtra("receiver", receiver);
context.startService(instructionServiceIntent);

Answer #4

對我來說,最簡單的解決方案是發送一個廣播,在我註冊的活動oncreate中定義了這樣的廣播(updateUIReciver被定義為一個類實例):

 IntentFilter filter = new IntentFilter();

 filter.addAction("com.hello.action"); 

 updateUIReciver = new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                //UI update here

            }
        };
 registerReceiver(updateUIReciver,filter);

從服務你發送這樣的意圖:

Intent local = new Intent();

local.setAction("com.hello.action");

this.sendBroadcast(local);

不要忘記註銷摧毀活動中的恢復:

unregisterReceiver(updateUIReciver);

Answer #5

我的解決方案可能不是最乾淨的,但它應該沒有問題。 邏輯就是創建一個靜態變量來將您的數據存儲在Service上,並在您的Activity每秒更新一次您的視圖。

假設您的Service上有一個String ,您希望將其發送到您的ActivityTextView 。 它應該看起來像這樣

您的服務:

public class TestService extends Service {
    public static String myString = "";
    // Do some stuff with myString

你的行為:

public class TestActivity extends Activity {
    TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        tv = new TextView(this);
        setContentView(tv);
        update();
        Thread t = new Thread() {
            @Override
            public void run() {
                try {
                    while (!isInterrupted()) {
                        Thread.sleep(1000);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                update();
                            }
                        });
                    }
                } catch (InterruptedException ignored) {}
            }
        };
        t.start();
        startService(new Intent(this, TestService.class));
    }
    private void update() {
        // update your interface here
        tv.setText(TestService.myString);
    }
}

Answer #6

克萊德的解決方案的工作原理,但它是一個廣播,我敢肯定,會比直接調用方法效率低。 我可能會誤解,但我認為廣播更適合於應用程序間通信。

我假設你已經知道如何用一個Activity綁定一個服務。 我做了一些類似於下面的代碼來處理這種問題:

class MyService extends Service {
    MyFragment mMyFragment = null;
    MyFragment mMyOtherFragment = null;

    private void networkLoop() {
        ...

        //received new data for list.
        if(myFragment != null)
            myFragment.updateList();
        }

        ...

        //received new data for textView
        if(myFragment !=null)
            myFragment.updateText();

        ...

        //received new data for textView
        if(myOtherFragment !=null)
            myOtherFragment.updateSomething();

        ...
    }
}


class MyFragment extends Fragment {

    public void onResume() {
        super.onResume()
        //Assuming your activity bound to your service
        getActivity().mMyService.mMyFragment=this;
    }

    public void onPause() {
        super.onPause()
        //Assuming your activity bound to your service
        getActivity().mMyService.mMyFragment=null;
    }

    public void updateList() {
        runOnUiThread(new Runnable() {
            public void run() {
                //Update the list.
            }
        });
    }

    public void updateText() {
       //as above
    }
}

class MyOtherFragment extends Fragment {
             public void onResume() {
        super.onResume()
        //Assuming your activity bound to your service
        getActivity().mMyService.mMyOtherFragment=this;
    }

    public void onPause() {
        super.onPause()
        //Assuming your activity bound to your service
        getActivity().mMyService.mMyOtherFragment=null;
    }

    public void updateSomething() {//etc... }
}

為了線程安全,我省略了一些,這是非常重要的。 確保在檢查和使用或更改服務上的片段引用時使用鎖或類似的東西。





android