OK, so I have a much larger application that I am not actually going to post here. In it, there is a vibration event that is looped through, though technically it is not a while
loop [the event ends at the users request] and the values that make up the long[] pattern = {0,dot, gap, dash, gap, dot, gap, dot};
change at every iteration so setting vibrator.vibrate(pattern, 0);
is not an option.
I set up another application to test some things about Vibrator. I found that running a static pattern gave a desired output, but putting the onVibrate
method in a while loop basically destroyed any ability to recognized the signal. I didn't find any way for the system to know if the phone was currently vibrating only if it could vibrate. So I decided to nest onVibrate
behind a boolean that the method would ultimate control and that control would be a callback that would 'pause' for more than the length the pattern took up. It ended up looking like this:
public class MainActivity extends Activity {
Boolean im_vibrating;
CallBack mCallBack;
Vibrator vibrator;
interface MyCallBack {
void offSet(Integer span);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCallBack = new CallBack();
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
im_vibrating = false;
while (true) {
if (!im_vibrating) {
im_vibrating = true;
onVibrate();
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public void onVibrate () {
int dot = 200;
int dash = 500;
int gap = 200;
long[] pattern = {
0,
dot, gap, dash, gap, dot, gap, dot
};
vibrator.vibrate(pattern, -1);
int span = dot + gap + dash + gap + dot + gap + dot + dot;
mCallBack.offSet(span);
}
class CallBack implements MyCallBack {
@Override
public void offSet(Integer span) {
final android.os.Handler handler = new android.os.Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
im_vibrating = false;
}
}, span);
}
}
}
However, despite having no errors, this doesn't work, something ends up blocking the rendering of the layout so the display is just blank, it vibrates once and then never again. Despite the fact that mCallBack
auto completed offSet
the SDK highlights offset
stating that it is never used.
Every operation done in the main thread, the one that renders views and in which the activities are called, must be as simple as it could. If this is not the case, the UI will be blocked.
Given that, the way to achieve something like you want is using a Handler
. This class allows you to dispatch messages to a given thread, in this case, the main one, even with delayed times.
One possible implementation for your solution:
long[] pattern = new long[]{
0,
dot, gap, dash, gap, dot, gap, dot
};
final Handler handler = new Handler();
handler.postDelayed(new Runnable(){
@Override
public void run(){
vibrator.vibrate();
if(!endVibration){
handler.postDelayed(this, timeToRun);
}
}
}, timeToRun);
This will generate a loop that can be cancelled by setting the endVibration variable to true and that is not blocking the UI.
You cannot use the UI thread to do long looping actions - move them into a separate thread.
Your scheme to use a Handler
as a sort of threaded callback will fail because Handler
instances use the thread they were created on to do their work. Since your handler is created by the UI thread and you've got the UI thread hanging in a while (true)
loop in your onCreate()
method, nothing will happen - the UI will never update (which is why the display is blank) and the handler will never run.
Just use an AsyncTask or aa separate Thread
to do the vibration and never block the UI thread.
OK taking into consideration what both parties above me said I ended up with this, which was exactly what I was looking for:
MainActivity.java
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Vibrator;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
Vibrator vibrator;
VibratePattern task;
Button stop_button;
Button start_button;
MyTaskParams params;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
stop_button = (Button) findViewById(R.id.button_stop);
stop_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
task.cancel(true);
task = null;
}
});
start_button = (Button) findViewById(R.id.button_start);
start_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (task == null) {
startTask();
}
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private static class MyTaskParams {
int dot, dash, gap;
MyTaskParams (int dot, int dash, int gap) {
this.dot = dot;
this.dash = dash;
this.gap = gap;
}
}
private void startTask() {
params = new MyTaskParams(200,500,200);
task = new VibratePattern();
task.execute(params);
}
public Integer onVibrate (Integer dot, Integer dash, Integer gap) {
long[] pattern = {
0,
dot, gap, dash, gap, dot, gap, dot
};
vibrator.vibrate(pattern, -1);
int span = dot + gap + dash + gap + dot + gap + dot + gap;
return span;
}
private class VibratePattern extends AsyncTask<MyTaskParams, Void, Integer> {
@Override
protected Integer doInBackground(MyTaskParams... params) {
int span;
span = onVibrate(params[0].dot,params[0].dash,params[0].gap);
return span;
}
@Override
protected void onPostExecute(Integer span) {
final android.os.Handler handler = new android.os.Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (!isCancelled()) {
startTask();
}
}
}, span);
}
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_start"
android:id="@+id/button_start"
android:layout_marginTop="90dp"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/button_stop"
android:layout_toStartOf="@+id/button_stop" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_stop"
android:id="@+id/button_stop"
android:layout_alignTop="@+id/button_start"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginRight="80dp"
android:layout_marginEnd="80dp" />
</RelativeLayout>
string.xml
<resources>
<string name="app_name">VibrationTest</string>
<string name="action_settings">Settings</string>
<string name="button_stop">Stop</string>
<string name="button_start">Start</string>
</resources>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="" >
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
startTask
could then load up new values every loop cycle dynamically changing the pulse as needed. Ty for your help.
it was nice for me
private void startTask() {
G.vibrator.vibrate(new long[]{0, 500, 200, 200, 500, 200, 200}, -1);
G.HANDLER.postDelayed(new Runnable() {
@Override
public void run() {
startTask();
}
}, 3000);
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.