項(xiàng)目里需要App端不斷地從服務(wù)器獲取數(shù)據(jù)虑椎,實(shí)時(shí)生成圖表榆鼠。圖表控件使用的是MPAndroidChart纲爸。自己寫了些實(shí)時(shí)更新折線圖的demo,數(shù)據(jù)是線程隨機(jī)生成的妆够,不是后臺(tái)數(shù)據(jù)识啦。
1、Message配合Handler實(shí)現(xiàn)
效果如下
在MainActivity中創(chuàng)建一個(gè)產(chǎn)生隨機(jī)數(shù)據(jù)的線程神妹,每產(chǎn)生一個(gè)數(shù)據(jù)發(fā)送一個(gè)Message袁滥,Handler收到Message之后更新折線圖。
MainActivity代碼如下:
public class MainActivity extends AppCompatActivity {
private static final int TAG = 1;//Message的what標(biāo)識(shí)
private TextView mTextView;
private Button mStartButton;
private LineChart mLineChart;
private Data[] mDatas;
private List<Entry> mEntries = new ArrayList<>();
private Thread mThread;
private Handler mHandler;
private Random mRandom;
private StringBuilder mStringBuilder;
private int mEndIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.test_txt);
mStartButton = findViewById(R.id.start_button);
mLineChart = findViewById(R.id.line_chart);
mRandom = new Random();
mStringBuilder = new StringBuilder("現(xiàn)在Y軸數(shù)字是0哦");
mEndIndex = 1;
//先創(chuàng)建5個(gè)Data數(shù)據(jù)
mDatas = new Data[]{new Data(1,5),new Data(2,8),
new Data(3,10),new Data(4,13),new Data(5,16)};
for (Data data :mDatas){
mEntries.add(new Entry(data.getValueX(),data.getValueY()));
}
LineDataSet dataSet = new LineDataSet(mEntries,"number");
LineData lineData = new LineData(dataSet);
mLineChart.setData(lineData);
mLineChart.invalidate();
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what==TAG){
updateTxt(msg);
updateChart(msg);
}
}
};
mThread = new Thread(new Runnable() {
@Override
public void run() {
int corrX = 6;//已經(jīng)有了五個(gè)數(shù)據(jù)灾螃,下一個(gè)數(shù)據(jù)的x坐標(biāo)從6開始
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int corrY = mRandom.nextInt(20) + 5;
Message message = Message.obtain();
message.arg1 = corrY;
message.arg2 = corrX;
message.what = TAG;
mHandler.sendMessage(message);
corrX += 1;
}
}
});
mStartButton.setOnClickListener((View v) -> mThread.start());
}
//更新SpannableString類型的文本需要用該函數(shù)判斷更新數(shù)字的位數(shù)
private int endIndex(int i){
int index = 0;
while (i!=0){
i = i/10;
index += 1;
}
return index;
}
//更新顯示當(dāng)前值的TextView
private void updateTxt(Message msg){
mStringBuilder.replace(7,7 + mEndIndex, msg.arg1 + "");//將原來(lái)的數(shù)字替換
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(mStringBuilder);
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.BLUE);
RelativeSizeSpan relativeSizeSpan = new RelativeSizeSpan(1.5f);
mEndIndex = endIndex(msg.arg1);//新的y值的位數(shù)
spannableStringBuilder.setSpan(foregroundColorSpan,7,7 + mEndIndex, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableStringBuilder.setSpan(relativeSizeSpan,7,7 + mEndIndex, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableStringBuilder);
}
//刷新折線圖
private void updateChart(Message msg){
mEntries.add(new Entry(msg.arg2,msg.arg1));
LineDataSet dataSet = new LineDataSet(mEntries,"number");
LineData lineData = new LineData(dataSet);
mLineChart.setData(lineData);
mLineChart.invalidate();
}
}
Data類如下
public class Data {
private int valueX;
private int valueY;
public Data(int x,int y){
this.valueX = x;
this.valueY = y;
}
public int getValueX() {
return valueX;
}
public void setValueX(int valueX) {
this.valueX = valueX;
}
public int getValueY() {
return valueY;
}
public void setValueY(int valueY) {
this.valueY = valueY;
}
}
2题翻、RxJava實(shí)現(xiàn)
Rxjava在處理復(fù)雜的多線程事件邏輯時(shí)比Handler/Async等要簡(jiǎn)單易用可靠。用來(lái)寫這個(gè)demo算是大炮打蚊子腰鬼,純當(dāng)練手了嵌赠。
MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private LineChart mLineChart;
private TextView mTextView;
private Button mStartButton;
private List<Entry> mEntryList = new ArrayList<>();
private Random mRandom;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLineChart = findViewById(R.id.line_chart);
mStartButton = findViewById(R.id.start_button);
mTextView = findViewById(R.id.value_txt);
mStartButton.setOnClickListener((View v) -> intervalObservable());
}
private void intervalObservable() {
mRandom = new Random();
Observable.interval(1000, 1000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
Log.e("intervalObservable", Thread.currentThread().getName());
Long x = aLong;
int y = mRandom.nextInt(10);
Data data = new Data(x, y);
x++;
mEntryList.add(new Entry(data.getX(), data.getY()));
LineDataSet dataSet = new LineDataSet(mEntryList, "label");
LineData lineData = new LineData(dataSet);
mLineChart.setData(lineData);
mLineChart.invalidate();
mTextView.setText("當(dāng)前y值為" + y);
}
});
}
}
寫的時(shí)候發(fā)現(xiàn)如果直接用Observable.create()生成數(shù)據(jù)的話,速度太快熄赡,MPAndroidChart刷新不過來(lái)姜挺,一片空白。所以改用Observable.interval()彼硫,每個(gè)1秒生成一個(gè)炊豪,但是這個(gè)函數(shù)只能返回一個(gè)Observable<Long>的對(duì)象,每次發(fā)射的都是Long類型的數(shù)據(jù)拧篮,所以把Data類型中的x值改成了Long類型词渤。
還要注意的是,Observable.interval()默認(rèn)訂閱Schedulers.computation這個(gè)線程串绩,如果有UI更新的話缺虐,需要在主線程中進(jìn)行觀察,即調(diào)用observeOn(AndroidSchedulers.mainThread())礁凡。
但是我發(fā)現(xiàn)一個(gè)很神奇的事高氮,MPAndroidChart可以在非UI線程中進(jìn)行刷新。
難道只能用Observal.interval()嗎?其實(shí)不是的顷牌,我發(fā)現(xiàn)只要使被觀察者線程休眠一小段時(shí)間剪芍,就能讓折線圖刷新出來(lái),代碼如下
Observable.create(new ObservableOnSubscribe<Data>() {
@Override
public void subscribe(@NonNull ObservableEmitter<Data> e) throws Exception {
Long x = 0l;
Random random = new Random();
while(x < 1000) {
int y = random.nextInt(10);
Data data = new Data(x, y);
e.onNext(data);
x++;
Thread.sleep(1000);//休眠1秒
}
Log.e(TAG, "Observable thread is : " + Thread.currentThread().getName());
}
}).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
Log.i("onNext(Data data)", data.toString());
Log.e(TAG, "Observer thread is :" + Thread.currentThread().getName());
mEntryList.add(new Entry(data.getX(), data.getY()));
LineDataSet dataSet = new LineDataSet(mEntryList, "label");
LineData lineData = new LineData(dataSet);
mLineChart.setData(lineData);
mLineChart.invalidate();
mTextView.setText("當(dāng)前值為" + data.getY());
});
其實(shí)在訂閱者線程中休眠也可以正常接收到數(shù)據(jù)窟蓝,但訂閱者線程一般是UI線程罪裹,休眠的話,UI就不會(huì)更新了。
另外坊谁,被觀察者的線程沒有休眠的話费彼,即使被觀察者數(shù)據(jù)發(fā)送的很快,訂閱者在onNext()即使進(jìn)行了線程休眠口芍,數(shù)據(jù)也能全部接收到箍铲,不會(huì)出現(xiàn)事件丟失的情況,這一點(diǎn)讓我比較疑惑鬓椭。
如果被觀察者的線程調(diào)用了Thread.sleep(1)颠猴,而觀察者在onNext()中調(diào)用了Thread.sleep(1000),那么會(huì)出現(xiàn)上下游事件處理速率不匹配小染,事件丟失翘瓮,OOM等情況。
這個(gè)時(shí)候就要用支持背壓的Flowable了裤翩。
其實(shí)用Flowable同樣可以實(shí)現(xiàn)折線圖更新资盅,代碼如下:
Flowable.create(new FlowableOnSubscribe<Data>() {
@Override
public void subscribe(FlowableEmitter<Data> e) throws Exception{
Long x = 0l;
Random random = new Random();
while(x < 1000) {
int y = random.nextInt(10);
Data data = new Data(x, y);
e.onNext(data);
x++;
Thread.sleep(1000);
}
Log.e(TAG, "Observable thread is : " + Thread.currentThread().getName());
}
}, BackpressureStrategy.BUFFER).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Data>() {
Subscription mSubscription;
@Override
public void onSubscribe(Subscription s) {
mSubscription = s;
s.request(1);
}
@Override
public void onNext(Data data) {
Log.i("onNext(Data data)", data.toString());
Log.e(TAG, "Observer thread is :" + Thread.currentThread().getName());
mEntryList.add(new Entry(data.getX(), data.getY()));
LineDataSet dataSet = new LineDataSet(mEntryList, "label");
LineData lineData = new LineData(dataSet);
mLineChart.setData(lineData);
mLineChart.invalidate();
mTextView.setText("當(dāng)前值為" + data.getY());
mSubscription.request(1);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});