2. Abstract
• Ce que je veux faire: afficher un document Office ou PDF dans une application
Android
• Comment pallier à l‟absence de gestion native des documents Office et PDF?
• Présentation des différentes solutions
• Un retour sur expérience d‟une aventure folle
Portabilité de Java vers Android
Utilisation du code natif “quand y‟a pas le choix!”
2
3. Johann Hilbold & Alain Boudard
Développeur Android Développeur Android
Développeur Swing à ses débuts Webdesigner
Oxiane Oxiane Studio
3
5. Contraintes
• Ne pas sortir de l‟application pour ouvrir un document (!)
• Ne pas sortir le document du SI (= pas de google docs)
• Afficher des documents Office (Office 2003, 2007) et PDF
5
6. Solutions
• Afficher un document Office « binaire » dans une app
• Afficher un document Office « xml » dans une app
• Afficher un PDF dans une app
6
7. Afficher un fichier Office « binaire » - 1
C‟est l‟exemple le plus simple:
Il suffit d‟utiliser la librairie Apache POI.
3 jars à télécharger:
• poi-scratchpad-3.8.jar,
• poi-3.8.jar
• et commons-codec.jar
Copier des fichiers de test dans le répertoire Assets
7
8. Afficher un fichier Office « binaire » - 2
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//copier les fichiers de test depuis mon répertoire assets vers la SDCard
copyAssetToSDCard("test.doc");
copyAssetToSDCard("testxls.xls");
//lancer la conversion avec les jars d'apache POI(source, dest)
WordToHtmlConverter.main(new String[]{"/mnt/sdcard/test.doc",
"/mnt/sdcard/test.html"});
ExcelToHtmlConverter.main(new String[]{"/mnt/sdcard/testxls.xls",
"/mnt/sdcard/testxls.html"});
}
8
9. Afficher un fichier Office « binaire » - 3
private final int BUF_SIZE = 8192;
private File copyAssetToSDCard(String assetName) {
File copiedFileStoragePath = new File("/mnt/sdcard/"+assetName);
BufferedInputStream bis = null;
OutputStream dexWriter = null;
try {
bis = new BufferedInputStream(getAssets().open(assetName));
dexWriter = new BufferedOutputStream(new FileOutputStream(copiedFileStoragePath));
byte[] buf = new byte[BUF_SIZE];
int len;
while((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
dexWriter.write(buf, 0, len);
}
dexWriter.close();
bis.close();
} catch (Exception e) {e.printStackTrace();
return null;}
Log.d("OfficeCopy", "copied "+assetName+" to /sdcard");
return copiedFileStoragePath;
}
9
10. Afficher un fichier Office « binaire » - 4
Et voila!
Maintenant, on peut afficher le résultat dans une webview
WebView v = (WebView) findViewById(R.id.ma_web_view);
v.loadUrl("file://mnt/sdcard/test.html");
DEMO
10
11. Afficher un document Office « xml » - 1
Apache POI offre des services de lecture/écriture de fichiers Open XML
Mais c‟est plus compliqué!
Si on essaye la méthode précédente (documents binaires)…
org.apache.poi.poifs.filesystem.OfficeXmlFileException: The supplied data appears
to be in the Office 2007+ XML. You are calling the part of POI that deals with OLE2
Office Documents. You need to call a different part of POI to process this data (eg
XSSF instead of HSSF)
11
12. Afficher un document Office « xml » - 2
Utilisation d‟Apache POI – XSSF
Le plan:
Utiliser le même raisonnement que pour les documents « binaires »:
• télécharger les jars pour les documents Open XML
http://poi.apache.org/overview.html#components OK!
• Les ajouter au build path OK!
• Créer une petite classe pour appeler les bonnes méthodes
org.apache.poi.ss.examples.html.ToHtml.main(String[] files) OK!
• Lancer le projet et prier! NOK!
12
13. Afficher un document Office « xml » - 3
Que s‟est-il passé?
Avant même de pouvoir installer l‟APK sur le device,
[Dex Loader] Unable to execute dex: null
[myProject] Conversion to Dalvik format failed: Unable to execute dex: null
Lors de la génération du fichier APK, tous les fichiers .class sont
transformés en un unique fichier .dex (Dalvik EXecutable).
Cette transformation est limitée à 64k références de méthodes (!)
Nos jars (de 24Mo) pèsent plus de 64k méthodes!
13
14. Afficher un document Office « xml » - 4
La solution du blog Android Developper
Il « suffit » de découper nos classes en plusieurs fichiers .jar
Puis de lancer la dx.bat sur chacun des jars
Il convient d‟utiliser le script Ant fourni dans le SDK
android-sdktoolsantbuild.xml
Et de le modifier de la sorte:
<dex-helper-mod input-dir="${basedir}/libsToSpecialDEX/1"
output-dex-file="${out.absolute.dir}/onary_dex_dir/classes.dex"/>
<jar destfile="${asset.absolute.dir}/onary_dex.jar"
basedir="${out.absolute.dir}/onary_dex_dir" includes="classes.dex"
/>
14
15. Afficher un document Office « xml » - 5
Si tout s‟est bien passé…
On peut maintenant charger les classes en mémoire à l‟exécution
private Class loadClass(String className) {
new File("/sdcard/office/opti").mkdirs();
final File optimizedDexOutputPath = new File("/sdcard/office/opti");
DexClassLoader cl = new DexClassLoader("/sdcard/office/one.jar:/sdcard/office/two.jar",
optimizedDexOutputPath.getAbsolutePath(),
null,
TestOfficeAndroidActivity.this.getClassLoader());
Class libProviderClazz = null;
try {
libProviderClazz = cl.loadClass(className);
}
catch(Exception e)
{e.printStackTrace();}
return libProviderClazz;
}
15
16. Afficher un document Office « xml » - 6
Une fois la classe chargée en mémoire…
Class cl = loadClass( "org.apache.poi.ss.examples.html.ToHtml");
Class[] paramTypes = new Class[] { String[].class };
Method main = cl.getMethod("main", paramTypes);
main.invoke(o, (Object) new String[] {"/sdcard/office/SampleSS.xlsx",
"/sdcard/office/SampleSS.html"});
Et c‟est seulement à partir d‟ici que ça devient vraiment intéressant!
W/System.err(370): Caused by: java.lang.RuntimeException: Installation Problem???
Couldn‟t load messages: Can‟t find resource for bundle
„org.apache.xmlbeans.impl.regex.message_en_US‟, key »
16
17. Afficher un document Office « xml » - 7
Que peut bien vouloir dire cette erreur?
Le fichier message.properties est introuvable !
Il n‟est plus présent dans mon fichier dex!
Mais présent dans XmlBeans-1.0.jar
La compilation en fichier APK, c‟est un zip avec
un fichier .dex pour les .class (et uniquement les .class!)
tout le reste (images, xml de layout, fichiers raw) dans des dossiers
/res/drawable, /res/layout, /raw…
Le fichier .properties n‟est donc pas resté dans le .dex!
17
18. Afficher un document Office « xml » - 8
On peut tricher…
XmlBeans est OpenSource!
public void setLocale(Locale locale) {
try {//this.resources = ResourceBundle.getBundle("org.apache.xmlbeans.impl.regex.message", locale);
this.resources = new ResourceBundle() {
@Override
protected Object handleGetObject(String key) {
return res.get(key);
}
@Override
public Enumeration<String> getKeys() {
return Collections.enumeration(res.keySet());
}
};
} catch (MissingResourceException mre) {
throw new RuntimeException("Installation Problem??? Couldn't load messages: "+mre.getMessage());
}
}
static HashMap<String, String> res = new HashMap<String, String>();
static{res.put("parser.parse.1", "Wrong character.");}
18
19. Afficher un document Office « xml » - 9
Cette fois-ci, ça va passer…
Toujours pas!
org.apache.xmlbeans.SchemaTypeLoaderException: XML-BEANS compiled schema: Could not locate
compiled schema resource {…..}/index.xsb
Les fichiers .xsb ne sont pas dans les .dex, mais cette fois, on sait pourquoi!
package org.apache.xmlbeans.impl.schema;
public class ClassLoaderResourceLoader{
public InputStream getResourceAsStream(String name) {
try {
return new
FileInputStream("/sdcard/office/xsb/"+name.substring(name.lastIndexOf("/")+1));
} catch (FileNotFoundException e) {e.printStackTrace();
} //_classLoader.getResourceAsStream(resourceName);
return null;
}
19
20. Afficher un document Office « xml » - 10
On tient le bout…
Maintenant qu‟on accède à toutes les ressources du jar original…
ClassNotFoundException: java.awt.Color
POI est censé tourner sous Java 1.5
DEMO
Android ne connait pas les packages java.awt (et quelques autres)
Une ultime manipulation:
remplacer tous les java.awt par des and.awt dans les sources de POI
http://code.google.com/p/awt-android-compat/
Attention, le résultat n‟est pas parfait
pas de gestion des images (pour l‟instant)
20
21. Afficher un document Office « xml » - 11
Une approche plus sexy:
- Intégrer les ressources manquantes aux jars
- Création d‟un jar simple
- « Dexisation » du jar en un fichier classes.dex
- Réimportation des ressources dans un nouveau jar créé à la main NOK!
Le dexloader n‟a pas l‟air de gérer les jars et les APK de la même façon
- Packaging du classes.dex dans un APK « maison » OK!
- En réutilisant cette technique, on peut donc, en théorie, se passer des étapes précédentes
DEMO
21
22. Afficher un document Office « xml » - 12
Conclusion de la méthode POI
Le code complet est disponible ici:
https://code.google.com/p/display-msoffice-docs-android-with-apache-poi/
Avantages:
• Permet une conversion très complète (celle que permet le client POI)
• Est très rapide (adaptée aux processeurs « lents » des mobiles d‟aujourd‟hui)
• Donne accès à tous les formats (doc/docx, ppt/pptx, xls/xlsx, et même Visio .vsd
ou Outlook msg)
• Est chargeable dynamiquement en mémoire
• Laisse la porte ouverte à un futur « mode édition »
22
23. Afficher un document Office « xml » - 13
Inconvénients:
• Utilise énormément de place sur la SDcard (8Mo d‟APK + 15Mo de ressources)
• Compliqué à mettre en œuvre
• Pour la partie AWT, repose sur le package and.awt
Pas de gestion des java.awt.BufferedImage. Il faudrait les convertir en
android.graphics.Bitmap!
23
24. Afficher un document Office « xml » - 14
L‟alternative
La transformation XSL basée sur la solution de Julien Chable
http://openxmldeveloper.org/blog/b/openxmldeveloper/archive/2006/11/21/openxmlandjava.aspx
• Un procédé beaucoup plus simple, mais moins efficace.
<!-- Body and paragraphs -->
<xsl:template match="w:body">
<html>
<body>
<xsl:for-each select="w:p">
<p>
<xsl:apply-templates select="w:pPr" />
<xsl:apply-templates select="w:r" />
</p>
</xsl:for-each>
</body>
</html>
</xsl:template>
24
25. Afficher un document Office « xml » - 15
Avantages:
• D‟une légèreté déconcertante: mon APK fait seulement 261k
• D‟une simplicité déconcertante: un seul jar à ajouter au Build Path et c‟est fini.
• Facilement améliorable: il « suffit » d‟ajouter les balises qu‟on veut gérer
Inconvénients:
• Ne gère que les OfficeOpen XML (donc pas les formats « binaires »)
• Il faut trouver (ou écrire soi même?) un XSL vraiment complet pour un résultat
parfait.
Les images ne sont pas du tout gérées
Seuls quelques styles sont implémentés, pas de formule, tableaux, wordart…
25
27. Afficher un document Office « xml » - 17
Utilisation des rID pour retrouver le style ou le nom de fichier pour une image avec le
fichier document.xml.rels
w:drawing crée une balise <img>
avec pour attribut id = rID
On extrait toutes les images de
/word/media vers la SDCard
2ème parsing:
scan de toutes les balises img et ajout de l‟attribut grâce à la correspondance rID
27
28. Afficher un document Office « xml » - 18
Malgré cette technique pour afficher les images, cette solution reste imparfaite
• Les positions des éléments ne sont pas gérées
• Les puces, formules, tableaux ne sont pas gérés
• Un exemple existant de traitement XSL de document duquel on pourrait s‟inspirer:
DocBook
Mon code est disponible ici
https://code.google.com/p/display-msoffice-docs-android-without-apache-poi/
DEMO
28
29. Afficher un PDF -1
Une bonne occasion de voir le NDK
• Sous windows, installer Cygwin
http://mindtherobot.com/blog/452/android-beginners-ndk-setup-step-by-step/
(étapes 2 et 3)
• Télécharger le projet APV
http://code.google.com/p/apv/
• Lancer la commande /script/build-native.sh du projet APV
• Si tout s‟est bien passé, on peut lancer le projet APV sous Eclipse!
29
30. Afficher un PDF -2
C‟est très bien mais c‟est pas ce qu‟on veut!
On vient de faire l‟équivalent d‟installer l‟appli « Acrobat Reader »
Fichier librairie qui permet de parser les PDFs
30
31. Afficher un PDF -2
Customiser un peu…
Extraction de la View PagesView
• Par défaut, apv offre une view qui fait de nombreux appels à une activité.
Un composant personnalisé
<com.oxiane.DisplayPDF
android:id="@+id/my_pdf_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:src="http://monpdf.com" />
31
32. Afficher un PDF -3
Inconvénients de la méthode
• Pas de gestion des liens HTTP
• Encore 1,7Mo à rajouter à l‟APK!
• Chargement se fait en rectangles
Avantages de la méthode
• Ça a le mérite de marcher!
32
33. Conclusion
• Une aventure pionnière!
• Un aperçu des possibilités qu‟offre l‟open source Java dans Android
• Des ouvertures pour arriver à des résultats plus professionnels
33