Jump to content

[Résolu] Fermeture forcée du programme (LoaderManager et SQLite)


Recommended Posts

Salut !

Bon, le début va peut-être paraître un peu long mais histoire que vous ayez plus de chances de cerner le problème, je pense que c'est une bonne idée d'exposer tout depuis le début.

Pour un premier projet d'application Android, je voulais faire une application de checklists (c'est assez simple, je trouvais l'idée bonne pour s'habituer au développement Android). Pour cela, je souhaite utiliser la base de données pour stocker les listes et les items.

Voulant aussi en profiter pour m'initier aux bonnes méthodes de programmation sans passer par des méthodes dépréciées, je suis parti avec l'API 11, soit Android 3.0 et ses Loader.

Seulement voila, le guide du développeur sur android.com ou tout autre tutoriel que j'ai pu trouver sur le sujet demandent de passer par un ContentProvider. Sauf que, selon ce même guide, les ContentProvider n'ont pas à être utilisés si les données n'ont pas pour but d'être partagées avec d'autres applications (si j'ai bien compris).

J'ai donc cherché un moyen d'utiliser LoaderManager avec un CursorLoader à ma sauce, pour utiliser la base de données (ce qui, apparemment, est conseillé).

J'ai donc cherché jusqu'à trouver (en recollant les morceaux de quelques recherches Google). Le problème c'est que ça compile sans soucis mais à l'exécution c'est autre chose. Je teste avec l'émulateur et l'AVD de l'API 11 (une tablette). Je clique donc sur l'icône de l'application pour la lancer, elle se lance mais elle plante.

Précisément, une icône de chargement indique (a priori) le chargement des données mais lorsque cette icône disparaît, boum : fermeture forcée.

J'ai créé une application test qui reproduit l'architecture de mon projet (sans les petits trucs en plus qui gêneraient plus qu'autre chose la compréhension du problème comme des colonnes en plus dans la table, etc.). Là aussi, le soucis est exactement le même : ça compile, mais ça plante au même moment.

Voici donc les différents fichiers du test :

res/layout/main.xml

<?xml version="1.0" encoding="utf-8" ?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<fragment android:name="com.flinx.test.TestFragment" android:id="@+id/list_test" android:layout_width="match_parent" android:layout_height="match_parent" />
</LinearLayout>

src/com/flinx/test/Test.java

package com.flinx.test;
import android.os.Bundle;
import android.app.Activity;
public class Test extends Activity
{
@Override
public void onCreate(Bundle saved_instance_state)
{
	super.onCreate(saved_instance_state);
	setContentView(R.layout.main);
}
}

src/com/flinx/test/DBHelper.java

package com.flinx.test;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase;
public class DBHelper extends SQLiteOpenHelper
{
public static final String DB_NAME = "test.db";
public static final int DB_VERSION = 1;
public static final String TABLE_TEST = "test";
public static final String COL_ID = "_id";
public static final String COL_LABEL = "label";
public DBHelper(Context context)
{
 super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db)
{
 String query =
  "CREATE TABLE " + TABLE_TEST + "(" +
COL_ID + " INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " +
COL_LABEL + " TEXT NOT NULL" +
  ");";
 db.execSQL(query);
}
@Override
public void onUpgrade(SQLiteDatabase db, int old_version, int new_version)
{
}
}

src/com/flinx/test/TestFragment.java

package com.flinx.test;
import android.os.Bundle;
import android.app.ListFragment;
import android.app.LoaderManager;
import android.database.Cursor;
import android.content.Loader;
import android.widget.SimpleCursorAdapter;
public class TestFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>
{
private SimpleCursorAdapter m_cursor_adapter;
private DBHelper m_db;
@Override
public void onActivityCreated(Bundle saved_instance_state)
{
 super.onActivityCreated(saved_instance_state);
 m_db = new DBHelper(getActivity());
 setEmptyText("Table vide");
 String[] from = new String[] {DBHelper.COL_LABEL};
 int[] to = new int[] {android.R.id.text1};
 m_cursor_adapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, from, to, 0);
 setListAdapter(m_cursor_adapter);
 setListShown(false);
 getLoaderManager().initLoader(0, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args)
{
 return new TestCursorLoader(getActivity(), m_db);
}
@Override
public void onLoaderReset(Loader<Cursor> loader)
{
 m_cursor_adapter.swapCursor(null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data)
{
 m_cursor_adapter.swapCursor(data);
 if (isResumed())
 {
  setListShown(true);
 }
 else
 {
  setListShownNoAnimation(true);
 }
}
}

src/com/flinx/test/TestCursorLoader.java

package com.flinx.test;
import android.database.Cursor;
import android.content.AsyncTaskLoader;
import android.content.Context;
public class TestCursorLoader extends AsyncTaskLoader<Cursor>
{
private Cursor m_cursor;
private DBHelper m_db;
public TestCursorLoader(Context context, DBHelper db)
{
 super(context);
 m_db = db;
}
@Override
public Cursor loadInBackground()
{
 Cursor cursor = m_db.getReadableDatabase().query(DBHelper.TABLE_TEST, new String[] {DBHelper.COL_LABEL}, null, null, null, null, null);
 return cursor;
}
@Override
public void deliverResult(Cursor cursor)
{
 if (isReset())
 {
  if (cursor != null)
  {
cursor.close();
  }
  return;
 }
 Cursor old_cursor = m_cursor;
 m_cursor = cursor;
 if (isStarted())
 {
  super.deliverResult(cursor);
 }
 if (old_cursor != null && old_cursor != cursor && !old_cursor.isClosed())
 {
  old_cursor.close();
 }
}
@Override
protected void onStartLoading()
{
 if (m_cursor != null)
 {
  deliverResult(m_cursor);
 }
 if (takeContentChanged() || m_cursor == null)
 {
  forceLoad();
 }
}
@Override
protected void onStopLoading()
{
 cancelLoad();
}
@Override
public void onCanceled(Cursor cursor)
{
 if (cursor != null && !cursor.isClosed())
 {
  cursor.close();
 }
}
@Override
protected void onReset()
{
 super.onReset();
 onStopLoading();
 if (m_cursor != null && !m_cursor.isClosed())
 {
  m_cursor.close();
 }
 m_cursor = null;
}
}

Si vous voulez tester, il suffit de faire un copier/coller des fichiers, normalement je n'en ai pas oublié, il n'y a rien de plus.

Je suis preneur de toute solution dans le meilleur des cas, mais aussi de toute idée ou piste qui pourrait faire avancer les choses. Ah, et n'hésitez pas non plus si jamais vous voyez qu'en fait, j'ai mal lu l'anglais, on sait jamais !

Merci d'avance à ceux qui auront le courage de lire et de m'aider, de quelque manière que ce soit.

Edited by Flinx
Link to comment
Share on other sites

Si tu as une fermeture forcée de ton application, c'est qu'il se produit une exception dans ton code. La trace de cette exception se retrouve dans le Logcat. En analysant cette trace, tu auras sans doute des réponses à ton problème (tu peux aussi nous indiquer quelle est cette erreur)

  • Like 1
Link to comment
Share on other sites

Ah merci, je ne connaissais pas, ça va m'être sûrement très utile. :)

J'ai donc lancé le logcat et voici les lignes qui apparaissent à partir du moment où je lance l'application (j'ai bien fait attention à ne rien prendre de plus pour éviter les lignes inutiles) :

I/ActivityManager(  230): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.flinx.test/.Test } from pid 316
W/WindowManager(  230): Failure taking screenshot for (216x135) to layer 21005
I/ActivityManager(  230): Start proc com.flinx.test for activity com.flinx.test/.Test: pid=514 uid=10036 gids={1015}
D/TabletStatusBar(  394): lights on
D/dalvikvm(  514): GC_FOR_ALLOC freed 44K, 4% free 6274K/6531K, paused 149ms
I/dalvikvm-heap(  514): Grow heap (frag case) to 6.706MB for 513744-byte allocation
D/dalvikvm(  514): GC_CONCURRENT freed <1K, 4% free 6775K/7047K, paused 47ms+19ms
V/TLINE   (  514): new: android.text.TextLine@40639c50
I/ActivityManager(  230): Displayed com.flinx.test/.Test: +4s554ms
D/dalvikvm(  316): GC_EXPLICIT freed 7K, 14% free 16493K/19143K, paused 269ms+41ms
D/dalvikvm(  316): GC_EXPLICIT freed <1K, 14% free 16493K/19143K, paused 8ms+19ms
D/AndroidRuntime(  514): Shutting down VM
W/dalvikvm(  514): threadid=1: thread exiting with uncaught exception (group=0x40014760)
E/AndroidRuntime(  514): FATAL EXCEPTION: main
E/AndroidRuntime(  514): java.lang.IllegalArgumentException: column '_id' does not exist
E/AndroidRuntime(  514):  at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:297)
E/AndroidRuntime(  514):  at android.widget.CursorAdapter.swapCursor(CursorAdapter.java:339)
E/AndroidRuntime(  514):  at android.widget.SimpleCursorAdapter.swapCursor(SimpleCursorAdapter.java:341)
E/AndroidRuntime(  514):  at com.flinx.test.TestFragment.onLoadFinished(TestFragment.java:49)
E/AndroidRuntime(  514):  at com.flinx.test.TestFragment.onLoadFinished(TestFragment.java:10)
E/AndroidRuntime(  514):  at android.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:409)
E/AndroidRuntime(  514):  at android.app.LoaderManagerImpl$LoaderInfo.onLoadComplete(LoaderManager.java:381)
E/AndroidRuntime(  514):  at android.content.Loader.deliverResult(Loader.java:97)
E/AndroidRuntime(  514):  at com.flinx.test.TestCursorLoader.deliverResult(TestCursorLoader.java:42)
E/AndroidRuntime(  514):  at com.flinx.test.TestCursorLoader.deliverResult(TestCursorLoader.java:7)
E/AndroidRuntime(  514):  at android.content.AsyncTaskLoader.dispatchOnLoadComplete(AsyncTaskLoader.java:219)
E/AndroidRuntime(  514):  at android.content.AsyncTaskLoader$LoadTask.onPostExecute(AsyncTaskLoader.java:59)
E/AndroidRuntime(  514):  at android.os.AsyncTask.finish(AsyncTask.java:590)
E/AndroidRuntime(  514):  at android.os.AsyncTask.access$600(AsyncTask.java:149)
E/AndroidRuntime(  514):  at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:603)
E/AndroidRuntime(  514):  at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(  514):  at android.os.Looper.loop(Looper.java:126)
E/AndroidRuntime(  514):  at android.app.ActivityThread.main(ActivityThread.java:3997)
E/AndroidRuntime(  514):  at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(  514):  at java.lang.reflect.Method.invoke(Method.java:491)
E/AndroidRuntime(  514):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
E/AndroidRuntime(  514):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
E/AndroidRuntime(  514):  at dalvik.system.NativeStart.main(Native Method)
W/ActivityManager(  230):   Force finishing activity com.flinx.test/.Test
W/WindowManager(  230): Failure taking screenshot for (216x135) to layer 21010
W/ActivityManager(  230): Activity pause timeout for ActivityRecord{4079b058 com.flinx.test/.Test}
I/Process (  514): Sending signal. PID: 514 SIG: 9
I/ActivityManager(  230): Process com.flinx.test (pid 514) has died.
E/InputDispatcher(  230): channel '409dee88 com.flinx.test/com.flinx.test.Test (server)' ~ Consumer closed input channel or an error occurred.  events=0x8
E/InputDispatcher(  230): channel '409dee88 com.flinx.test/com.flinx.test.Test (server)' ~ Channel is unrecoverably broken and will be disposed!
I/WindowManager(  230): WIN DEATH: Window{409dee88 com.flinx.test/com.flinx.test.Test paused=false}
I/WindowManager(  230): WINDOW DIED Window{409dee88 com.flinx.test/com.flinx.test.Test paused=false}
W/InputManagerService(  230): Got RemoteException sending setActive(false) notification to pid 514 uid 10036

Bon alors si je sais bien lire tout ça, le problème se situe dans le fait qu'il n'y a pas de colonne "_id" dans ma table... Sauf qu'a priori elle devrait exister (voir DBHelper.java donné dans le premier post)...

Une idée ?

Édit : J'ai trouvé la solution !

La colonne _id existe bel et bien dans la table mais le problème ne vient pas de là. En effet, lors de la récupération des données, il faut absolument récupérer la colonne _id, ce que je ne faisais pas (je demandais seulement la colonne label).

Il faut donc changer la requête pour remplir le Cursor :

Cursor cursor = m_db.getReadableDatabase().query(DBHelper.TABLE_TEST, new String[] {DBHelper.COL_ID, DBHelper.COL_LABEL}, null, null, null, null, null);

Inutile par contre de changer quoi que ce soit dans le SimpleCursorAdapter (autrement dit, pas la peine d'ajouter la colonne _id dans le tableau String[] nommé from dans mon exemple).

Merci pour ta réponse en tout cas, elle m'a bien aidé, j'étais un peu découragé de ne pas trouver après autant de recherches.

Edited by Flinx
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...