Application chronomètre éxécutable que lors de l'installation
Bonjour,
je rencontre un problème en développant une application chronomètre.
je sais qu'il existe plein d'applications Chronometre/CompteARebours sur le market, mais je voulais m'en créer un :
- Je ne peux me permettre de me connecter à Internet vu mon forfait téléphonique
- Cela me permettrait de progresser davantage sur Android
Je ne pense pas que mes fichiers ressources strings.xml et colors.xml vous soient indispensables en revanche :
code de chronometer_view.xml
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:background="@color/chronometer_view_layout_background"
android:orientation="vertical" android:layout_width="fill_parent">
<TextView
android:id="@+id/valeur_chrono"
android:textStyle="bold"
android:typeface="monospace"
android:text="00:00:00"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="50sp"
android:gravity="center_horizontal"
android:background="@color/chronometer_background"
android:textColor="@color/chronometer_foreground"/>
<LinearLayout
android:layout_width="fill_parent"
android:background="@color/chronometer_layout_background"
android:orientation="horizontal"
android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal">
<Button
android:id="@+id/action_chrono"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/valeur_chrono"
android:text="@string/chronometer_action_text_start"
android:textSize="8sp"/>
<Button
android:id="@+id/initialisation_chrono"
android:layout_below="@id/valeur_chrono"
android:layout_toRightOf="@id/action_chrono"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/initialisation_chrono_text"
android:textSize="8sp"/>
<Button
android:id="@+id/configuration_chrono"
android:layout_below="@id/valeur_chrono"
android:layout_toRightOf="@id/initialisation_chrono"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/configuration_chrono_text"
android:textSize="8sp"/>
</LinearLayout>
</LinearLayout> |
code de ChronometerActivity.java
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
|
package org.isgreat.loloof64.android.chronometer;
import java.util.Observable;
import java.util.Observer;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class ChronometerActivity extends Activity implements OnClickListener, Observer {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chronometer_view);
actionButton = (Button) findViewById(R.id.action_chrono);
resetButton = (Button) findViewById(R.id.initialisation_chrono);
setupButton = (Button) findViewById(R.id.configuration_chrono);
chronometerValue = (TextView) findViewById(R.id.valeur_chrono);
actionButton.setOnClickListener(this);
resetButton.setOnClickListener(this);
setupButton.setOnClickListener(this);
////// temporaire /////////////////
resetButton.setEnabled(false);
setupButton.setEnabled(false);
///////////////////////////////////
}
@Override
public void onResume(){
super.onResume();
chronometerMotor = new ChronometerMotor();
chronometerMotor.addObserver(this);
chronometer = new Thread(chronometerMotor);
}
@Override
public void onClick(final View view) {
new Thread(){
{
setDaemon(true);
}
public void run(){
if (view == actionButton){
toggleChronometerState();
}
else if (view == resetButton){
resetChronometer();
}
else if (view == setupButton){
showSetupScreen();
}
}
}.start();
}
@Override
public void update(Observable observable, final Object data) {
runOnUiThread(new Thread(){
public void run(){
long totalSeconds = (Long) data;
String timeString = getTimeString(totalSeconds);
chronometerValue.setText(timeString);
}
});
}
private String getTimeString(long totalSeconds) {
int seconds = (int) totalSeconds % 60;
int minits = (int) (totalSeconds / 60) % 60;
int hours = (int) totalSeconds / 3600;
return String.format("%02d:%02d:%02d", hours, minits, seconds);
}
private void showSetupScreen() {
throw new UnsupportedOperationException();
}
private void resetChronometer() {
throw new UnsupportedOperationException();
}
private void toggleChronometerState() {
if ( ! chronometer.isAlive() )
chronometer.start();
else
chronometerMotor.toggleState();
}
private Button actionButton;
private Button resetButton;
private Button setupButton;
private TextView chronometerValue;
private ChronometerMotor chronometerMotor;
private Thread chronometer;
} |
code de ChronometerMotor.java
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
package org.isgreat.loloof64.android.chronometer;
import java.util.Observable;
public class ChronometerMotor extends Observable implements Runnable {
public ChronometerMotor(){
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException exception) {
exception.printStackTrace();
}
/*
* Causera certainement des soucis de Garbage Collection
* Mais en attedant de trouver mieux ...
*/
new UpdateThread().start();
}
}
public void toggleState(){
//Non supporté pour le moment
}
private class UpdateThread extends Thread{
public UpdateThread(){
setDaemon(true);
}
public void run(){
totalSeconds++;
setChanged();
notifyObservers(totalSeconds);
}
}
private long totalSeconds;
} |
Le problème c'est que le code s'éxécute normallement sitôt que je le lance depuis Eclipse, et que je clique sur le bouton Demarrer (le bouton actionButton dans l'activité). Mais dès que je quitte l'application avec le bouton Return ou Home, et je clique sur le raccourcis de mon émulateur, j'ai une exception
Affichage du Logcat pour l'exception
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
11-26 15:42:18.141: ERROR/AndroidRuntime(6113): Uncaught handler: thread main exiting due to uncaught exception
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{org.isgreat.loloof64/org.isgreat.loloof64.ChronometerActivity}: java.lang.ClassNotFoundException: org.isgreat.loloof64.ChronometerActivity in loader dalvik.system.PathClassLoader@437358a0
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2194)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2284)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.app.ActivityThread.access$1800(ActivityThread.java:112)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1692)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.os.Handler.dispatchMessage(Handler.java:99)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.os.Looper.loop(Looper.java:123)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.app.ActivityThread.main(ActivityThread.java:3948)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at java.lang.reflect.Method.invokeNative(Native Method)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at java.lang.reflect.Method.invoke(Method.java:521)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:782)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:540)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at dalvik.system.NativeStart.main(Native Method)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): Caused by: java.lang.ClassNotFoundException: org.isgreat.loloof64.ChronometerActivity in loader dalvik.system.PathClassLoader@437358a0
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at dalvik.system.PathClassLoader.findClass(PathClassLoader.java:243)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at java.lang.ClassLoader.loadClass(ClassLoader.java:573)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at java.lang.ClassLoader.loadClass(ClassLoader.java:532)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.app.Instrumentation.newActivity(Instrumentation.java:1097)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2186)
11-26 15:42:18.169: ERROR/AndroidRuntime(6113): ... 11 more |
J'ai aussi une deuxième question :
Vous avez certainement remarqué le commentaire dans la classe ChronometerMotor, à propos du ramasse-miettes. Je crée un nouveau Thread pour libérer la boucle principale de comptage, et ainsi réaliser les opérations de mise à jour de manière plus efficace. Mais, comment obtenir le même résultat en évitant de libérer l'instance courante de Thread et créer une nouvelle à chaque fois ? J'ai essayé de m'aventurer dans le Thread.wait() et Thread.notify ... sans grand succès.
J'ai essayé autre chose : sans succes
(Je n'avais pas lu ton dernier message)
J'ai fais des modifications, sans y parvenir (le chronomètre ne demarre pas)
classe ChronometerMotor.java
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
package org.isgreat.loloof64.android.chronometer;
import java.util.Observable;
import android.os.Handler;
public class ChronometerMotor extends Observable implements Runnable {
public void setChanged(){
super.setChanged();
}
public void run(){
while( ! appFinished ){
if ( ! appSuspended ){
myHandler.postDelayed(chronometerSnoozer, delayMilliSeconds);
}
}
}
private class MyChronometerMotorSnoozer implements Runnable {
@Override
public void run() {
updater.releaseWaiting();
myHandler.removeCallbacks(chronometerSnoozer);
myHandler.postDelayed(chronometerSnoozer, delayMilliSeconds);
}
private MyChronometerMotorUpdater updater = new MyChronometerMotorUpdater();
}
private class MyChronometerMotorUpdater extends Thread {
@Override
public void run(){
while ( ! appFinished ) {
if ( ! isWaiting ) {
totalSeconds++;
setChanged();
notifyObservers();
isWaiting = true;
}
}
}
public void releaseWaiting(){
isWaiting = false;
}
private boolean isWaiting;
}
public void toggleState(){
appSuspended = ! appSuspended;
}
private long totalSeconds;
private int delayMilliSeconds = 1000;
private Handler myHandler = new Handler();
private MyChronometerMotorSnoozer chronometerSnoozer = new MyChronometerMotorSnoozer();
private boolean appFinished;
private boolean appSuspended = true;
} |
En fait je veux vraiment lancer le "compteur" et la mise à jour dans deux Threads différents (donc disposer de 3 Threads) car dans ce cas j'utilise le Design Pattern Observer pré-implémenté par Java : java.util.Observer et java.util.Observable. Et donc, l'appel à notifyObservers risque de ralentir un peu le compteur si j'ai plus d'un Observer sur le ChronometerMotor. Mais, je suis peut être ambitieux.
Si toi ou quelqu'un d'autre a la solution , je serais plus que preneur (en l'ayant essayé bien sûr).
Ensuite, je m’intéresserais aux Services pour le lancer en tâche de fond (merci :) )