Teil 1Grüße!
Lassen Sie uns heute wie immer über das Erstellen mobiler Anwendungen mit dem Kivy- und Python-Framework sprechen. Insbesondere wird der Schwerpunkt darauf liegen, einen mobilen Client für eine Internetressource zu erstellen und auf Google Play zu veröffentlichen. Ich werde Ihnen sagen, mit welchen Problemen ein Anfänger und ein erfahrener Entwickler konfrontiert sein könnten, der sich entschlossen hat, sich in der plattformübergreifenden Entwicklung mit Kivy zu versuchen, was bei der Programmierung mit Python für Android getan werden kann und was nicht.
Eines Morgens fand ich in meiner Mail auf Habré einen Brief mit der Frage, ob ich Python und Kivy verwenden könnte, um "die Site svyatye.com in einer mobilen Anwendung neu zu erstellen, damit die Leute sie offline lesen und verwenden können", mit der anschließenden Veröffentlichung des Clients in Google Play App Store. Nach dem Link und dem Durchsuchen der Ressource, die sich als große Bibliothek mit Zitaten herausstellte, stellte ich mir vor, wie sie in einer mobilen Präsentation aussehen würde und wie ich manchmal Listen mit „mehr als 30.236 Sprüchen der heiligen Väter und Lehrer der Kirche“ trotz der Länge der Zitate erstellen würde erreichte über 10.000 Zeichen (5-6 Seiten gedruckten Textes). Da ich schon lange mit Kivy zusammenarbeite, wurde mir schnell klar, wie und was ich tun würde. Daher antwortete er dem Kunden, dass es nicht schwierig sei, einen solchen Antrag zu stellen. Es traten jedoch Schwierigkeiten auf, die ich weiter unten erörtern werde ...
Es wurden keine technischen Spezifikationen angegeben. Die einzige Voraussetzung ist, dass die Anwendung wie eine Uhr funktioniert. Keine Fristen. Es gab auch keine Schnittstellenlayouts. "Alles sollte so einfach wie möglich sein, ohne Animationen, Transformationen und andere Hülsen, mit einem Wort, so streng wie möglich." Umso besser. Darüber hinaus ist meine Entscheidung bereits reif - die Anwendung verwendet ein RecycleView-Objekt, das Kategorien, Unterkategorien, Autorenlisten von Zitaten und Zitate selbst anzeigt.
Listen
RecycleView, mit dem Sie in Sekundenbruchteilen große Listen mit vielen Tausenden öffnen können, verhielt sich jedoch ganz anders als gewünscht. Nein, es gab keine Probleme beim Öffnen von Angebotslisten, alles funktionierte schnell. Ich habe nicht einmal neue Angebote mit dem Fenster "Warten" geladen, wie auf der Website, da die Liste der Angebote der ausgewählten Kategorie sofort und vollständig gerendert wurde. Das Problem war anders - der Kunde bestand darauf, dass der Text des Angebots in der Liste vollständig angezeigt werden sollte und dass RecycleView hier nicht ganz angemessen war. Tatsache ist, dass das Funktionsprinzip dieses Widgets wie folgt lautet: Ein Objekt wird auf der gesamten Liste erstellt, das in Zukunft einfach geklont wird, wodurch wir eine erstaunliche Geschwindigkeit beim Rendern der Liste haben, egal wie groß sie ist. Eines ist jedoch wichtig: Die Höhe des Listenelements muss im Voraus festgelegt und bekannt sein. Wenn Sie jedoch wie in meinem Fall die Höhe des nächsten Listenelements beim Scrollen dynamisch berechnen müssen, tritt eine merkliche Verzögerung auf - die Liste wird für den Bruchteil einer Sekunde eingefroren, was, wie Sie sehen, keineswegs prodaktionsbereit ist.
Mit halbem Kummer gelang es mir, den Kunden zu einer Liste mit einer Vorschau von Zitaten zu überreden, deren Text vollständig durch Tapu zum Text der Vorschau geöffnet wurde, wie dies in fast jedem Forum der Fall war, nicht weil RecycleView die Aufgabe nicht bewältigen konnte, sondern weil dies der Fall war Das logischste: Durch den mehrseitigen Text des Zitats zu scrollen, insbesondere wenn das Zitat den Benutzer nicht interessierte, war es aus meiner Sicht nicht korrekt.
Abb. 1
Vorschau und Volltext beim Tippen auf die AngebotsvorschauDiese Option hat sehr schnell funktioniert, aber ... dem Kunden hat sie nicht gefallen ... Ich musste eine langsame ScrollView verwenden, die die Liste rendert, BEVOR sie auf dem Bildschirm angezeigt wird. Dies bedeutet, dass das Scrollen der Liste der Anführungszeichen nicht eingefroren wird, da alle Parameter der Listenelemente im Voraus berechnet und gerendert werden. Dies wirkt sich natürlich auf die Geschwindigkeit der Auflistung des Bildschirms aus. Normalerweise steht die Leistung an erster Stelle, und hier sagen sie zu mir: "Lass es langsamer sein."
Nun, ich habe nicht mehr gestritten und obwohl mir diese Lösung wirklich nicht gefallen hat, musste ich alles auf ScrollView übertragen. Da ScrollView, wie gesagt, sehr langsam ist, wurde beschlossen, Anführungszeichen in Abschnitten von jeweils zehn anzuzeigen, wobei die nächsten zehn automatisch geladen werden.
Wenn die ersten Rückmeldungen von Benutzern mit einer Anfrage eingingen, sagten sie, dass die Lesezeichen nicht wirklich schaden würden, wie es mir scheint. Der Kunde bezweifelte dennoch die Richtigkeit der Entscheidung für die Verwendung von ScrollView, da wir die Miniaturansichten von Zitaten und RecycleView hinterlassen haben. Dann konnten sie ohne Probleme sofort die Lesezeichen wiederherstellen, die der Benutzer zuvor in den vorherigen Sitzungslisten mit Zitaten angezeigt hatte, unabhängig davon, wie lange sie dauerten. Und mit ScrollView wird der Benutzer einfach alt, während er darauf wartet, dass die Liste zumindest im Geiste der Anführungszeichen angezeigt wird.
Buildozer und Dienstleistungen
Als sich die Anwendung entwickelte, gab es einen Vorschlag, einen Dienst darin einzureichen, der dem Benutzer einmal täglich ein zufälliges Angebot aus der Datenbank sendet. Ich hatte mich noch nie zuvor in Kivy mit ähnlichen Aufgaben befasst, aber als ich mich daran erinnerte, dass es zu diesem Thema
einen Artikel über Habré gibt, beschloss ich, es zu versuchen.
Nachdem ich eine ganze Woche lang fünf Tastaturen und zwei Monitore kaputt gemacht hatte, konnte ich das Paket nicht gemäß den Anweisungen aus dem obigen Artikel kompilieren - während der Kompilierung wurde die erforderliche Klasse nicht gefunden. Nachdem ich an den Autor des Artikels geschrieben hatte, schlug ich vor, dass anscheinend nur zwei Gründe zutrafen, aus denen ich versagte: Entweder ich war ein Idiot oder die Entwickler brachen Buildozer, ein Tool zum Erstellen von APK-Paketen für Android. Meine Vermutungen erwiesen sich als wahr - "Natürlich haben sie es gebrochen, nach der Version 0.33, was zum Teufel sie sammeln werden."
Ja, der Löwenanteil der Fragen im
Kivy-Forum hängt mit verschiedenen Problemen zusammen, die genau bei Buildozer auftreten. Jetzt benötigt jede Version dieses Tools eine eigene Version von Cython, die Sie lange Zeit experimentell auswählen. Mit den neuesten Buildozer-Versionen können Sie Ihrem JAR-Projekt keine Bibliothek hinzufügen, da die Bibliothek zwar zusammengestellt wird, die Bibliothek jedoch nicht hinzugefügt wird und Sie noch eine Woche Zeit haben Sitzen Sie wie ich herum und suchen Sie nach einem Problem. Und ... du wirst sie nicht finden. Für Anfänger und Menschen mit einer schwachen Mentalität kann die Arbeit mit Buildozer daher in die Klinik bringen.
Also spuckte ich auf diesen toten Traktor, fuhr zur Hölle, ging zu Github, lud Python für Android herunter, nahm Crystax-NDK auf die Off-Site, installierte Python 3.5 und stellte ruhig die APK des Projekts mit dem dritten Python-Zweig zusammen, was sich als viel einfacher herausstellte. als mit dem berüchtigten Buildozer.
Was ist mit Dienstleistungen? Aber nichts. Sie arbeiten nicht. Genauer gesagt, der in Ihrem Projekt erstellte Dienst beginnt nicht mit einem Neustart des Smartphones, unabhängig davon, was der Autor des Artikels über Dienste in Kivy behauptet. Nachdem ich bei Google Play gefunden und das Projekt installiert hatte, stellte ich fest, dass keine Dienste beim Neustart des Programms gestartet werden. 100% der Dienste in Kivy beginnen erst mit dem Start der Anwendung. Wenn Sie die Anwendung später schließen, arbeitet der Dienst weiterhin leise, bis Sie das Gerät ausschalten.
Über Python 2 und Python 3
Im Februar dieses Jahres fand Moskau Python im Moskauer Büro von Yandex statt, in dem Vladislav Shashkov eine Präsentation zum Thema hielt: „Python-Mobilanwendung mit Kivy / Buildozer ist der Schlüssel zum Erfolg“. Er hatte also die Dummheit zu sagen, dass Python 2 in der APK-Assembly schneller ist als Python 3. Glauben Sie niemandem, das ist nicht wahr. Python 3 ist im Prinzip schneller als Python 2! Als ich "Zitate der Heiligen" entwickelte (dann wurde auch angenommen, dass der zweite Python-Zweig in der Assembly verwendet werden würde), war ich entsetzt, als ich feststellte, dass die 20-MB-Angebotsbasis, die in der Anwendung verwendet wird, wenn keine Netzwerkverbindung besteht, mit json gelesen wird. Lädt bis zu 13-16 Sekunden auf ein mobiles Gerät! Und die gleiche Basis, aber bereits mit Python 3 wird es in 1-2 Sekunden auf dem Gerät verarbeitet! Ziehen Sie Ihre eigenen Schlussfolgerungen ...
Über React Native
Ja, in meinen Artikeln habe ich beschlossen, Parallelen zwischen Kivy und anderen Frameworks für die plattformübergreifende Entwicklung zu ziehen. Hier müssen Sie nur den Spoiler öffnen und sehen, wie einfach, schnell und elegant Anwendungen auf React Native erstellt werden ...
BeispielVersuchen wir, eine vollständige Schnittstelle zu zeichnen. Wir schreiben App.js mit den Komponenten aus der Native-Base-Bibliothek neu:
import React from 'react'; import {Container, Content} from 'native-base'; import {StyleSheet, Text, View} from 'react-native'; import AppFooter from './components/AppFooter.js'; const styles = StyleSheet.create({ container: { padding: 20 }, }); const App = () => ( <Container> <Content> <View style={styles.container}> <Text> Lorem ipsum... </Text> </View> </Content> <AppFooter/> </Container> ); export default App;
Wir sehen die neue AppFooter-Komponente, die wir erstellen müssen. Wir gehen zum Ordner ./components/ und erstellen die Datei AppFooter.js mit folgendem Inhalt:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; const AppFooter = () => ( <Footer> <FooterTab> <Button active> <Text></Text> </Button> <Button> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Alles ist bereit, um zu versuchen, unsere Anwendung zu erstellen!
Unsere Tasten können noch nicht wechseln. Es ist Zeit, sie zu unterrichten. Dazu müssen Sie zwei Dinge tun: Erfahren Sie, wie Sie mit dem Klickereignis umgehen und wie Sie den Status speichern. Beginnen wir mit dem Staat. Da wir uns geweigert haben, den Status in der Komponente zu speichern, nachdem wir uns für reine Komponenten und einen globalen Speicher entschieden haben, werden wir Redux verwenden.
Zunächst müssen wir unsere Seite schaffen.
import {createStore} from 'redux'; const initialState = {}; const store = createStore(reducers, initialState);
Lassen Sie uns ein Leerzeichen für Reduzierungen erstellen. Erstellen Sie im Reduzierungsordner die Datei index.js mit folgendem Inhalt:
export default (state = [], action) => { switch (action.type) { default: return state } };
Reduzierstücke an App.js anschließen:
import reducers from './reducers';
Jetzt müssen wir unseren Speicher auf Komponenten verteilen. Dies erfolgt speziell mit der Provider-Komponente. Wir verbinden es mit dem Projekt:
import {Provider} from 'react-redux';
Und wickeln Sie alle Komponenten in einen Provider. Die aktualisierte App.js sieht folgendermaßen aus:
import React from 'react'; import {Container, Content} from 'native-base'; import {StyleSheet, Text, View} from 'react-native'; import AppFooter from './components/AppFooter.js'; import {createStore} from 'redux'; import {Provider} from 'react-redux'; import reducers from './reducers'; const initialState = {}; const store = createStore(reducers, initialState); const styles = StyleSheet.create({ container: { padding: 20 }, }); const App = () => ( <Provider store={store}> <Container> <Content> <View style={styles.container}> <Text> Lorem ipsum... </Text> </View> </Content> <AppFooter/> </Container> </Provider> ); export default App;
Jetzt kann unsere Anwendung ihren Status speichern. Lassen Sie uns dies ausnutzen. Wir fügen den Modusstatus hinzu, der standardmäßig auf ARTIKEL eingestellt ist. Dies bedeutet, dass unsere Anwendung beim ersten Rendern so eingestellt wird, dass die Liste der Artikel angezeigt wird.
const initialState = { mode: 'ARTICLES' };
Nicht schlecht, aber das manuelle Schreiben von Zeichenfolgenwerten führt zu potenziellen Fehlern. Lassen Sie uns Konstanten bekommen. Erstellen Sie eine ./constants/index.js-Datei mit folgendem Inhalt:
export const MODES = { ARTICLES: 'ARTICLES', PODCAST: 'PODCAST' };
Und schreiben Sie App.js neu:
import {MODES} from './constants'; const initialState = { mode: MODES.ARTICLES };
Nun, es gibt einen Zustand, es ist Zeit, ihn an die Fußzeilenkomponente zu übergeben. Werfen wir einen weiteren Blick auf unsere ./components/AppFooter.js:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; const AppFooter = () => ( <Footer> <FooterTab> <Button active> <Text></Text> </Button> <Button> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Wie wir sehen können, wird der Status des Schalters anhand der aktiven Eigenschaft der Button-Komponente bestimmt. Lassen Sie uns den aktuellen Status der Anwendung auf Button verschieben. Dies ist nicht schwierig. Die Hauptkomponente der Haube ist die Provider-Komponente, die wir zuvor angeschlossen haben. Es bleibt nur, den aktuellen Status daraus zu übernehmen und die AppFooter-Komponenten in die Eigenschaften (Requisiten) einzufügen. Zunächst ändern wir unseren AppFooter so, dass der Status der Schaltflächen gesteuert werden kann, indem der Modus durch Requisiten geleitet wird:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; import {MODES} from "../constants"; const AppFooter = ({mode = MODES.ARTICLES}) => ( <Footer> <FooterTab> <Button active={mode === MODES.ARTICLES}> <Text></Text> </Button> <Button active={mode === MODES.PODCAST}> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Beginnen wir nun mit der Erstellung eines Containers. Erstellen Sie die Datei ./containers/AppFooterContainer.js.
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {MODES} from "../constants"; const AppFooterContainer = () => ( <AppFooter mode={MODES.ARTICLES} /> ); export default AppFooterContainer;
Verbinden Sie den AppFooterContainer-Container in App.js anstelle der AppFooter-Komponente. Bisher unterscheidet sich unser Container nicht von der Komponente, aber alles wird sich ändern, sobald wir ihn mit dem Status der Anwendung verbinden. Lass es uns tun!
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {connect} from 'react-redux'; const mapStateToProps = (state) => ({ mode: state.mode }); const AppFooterContainer = ({mode}) => ( <AppFooter mode={mode} /> ); export default connect( mapStateToProps )(AppFooterContainer);
Sehr funktional! Alle Funktionen sind sauber geworden. Was ist hier los? Wir verbinden unseren Container mithilfe der Connect-Funktion mit dem Status und verbinden seine Requisiten mithilfe der mapStateToProps-Funktion mit dem Inhalt des globalen Status. Sehr sauber und schön.
Wir haben also gelernt, Daten von oben nach unten zu verteilen. Jetzt müssen wir lernen, wie wir unseren globalen Zustand von unten nach oben verändern können. Aktionen sollen Ereignisse über die Notwendigkeit generieren, den globalen Status zu ändern. Erstellen wir eine Aktion, die beim Klicken auf eine Schaltfläche ausgeführt wird.
Erstellen Sie die Datei ./actions/index.js:
import { SET_MODE } from './actionTypes'; export const setMode = (mode) => ({type: SET_MODE, mode});
Und die Datei ./actions/actionTypes, in der wir die Konstanten mit den Aktionsnamen speichern:
export const SET_MODE = 'SET_MODE';
Die Aktion erstellt ein Objekt mit dem Namen des Ereignisses und dem Datensatz, der dieses Ereignis begleitet, und nicht mehr. Jetzt lernen wir, wie dieses Ereignis generiert wird. Wir kehren zum AppFooterContainer-Container zurück und verbinden die Funktion mapDispatchToProps, die Ereignis-Dispatcher mit den Requisiten des Containers verbindet.
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {connect} from 'react-redux'; import {setMode} from '../actions'; const mapStateToProps = (state) => ({ mode: state.mode }); const mapDispatchToProps = (dispatch) => ({ setMode(mode) { dispatch(setMode(mode)); } }); const AppFooterContainer = ({mode, setMode}) => ( <AppFooter mode={mode} setMode={setMode} /> ); export default connect( mapStateToProps, mapDispatchToProps )(AppFooterContainer);
Nun, wir haben eine Funktion, die das SET_MODE-Ereignis auslöst, und wir haben es zur AppFooter-Komponente übersprungen. Zwei Probleme bleiben bestehen:
Niemand ruft diese Funktion auf.
Niemand hört dem Ereignis zu.
Wir werden uns mit dem ersten Problem befassen. Wir gehen zur AppFooter-Komponente und verbinden den Aufruf mit der Funktion setMode.
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; import {MODES} from "../constants"; const AppFooter = ({mode = MODES.ARTICLES, setMode = () => {}}) => ( <Footer> <FooterTab> <Button active={mode === MODES.ARTICLES} onPress={ () => setMode(MODES.ARTICLES)}> <Text></Text> </Button> <Button active={mode === MODES.PODCAST} onPress={ () => setMode(MODES.PODCAST)}> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Wenn nun die Taste gedrückt wird, wird das Ereignis SET_MODE ausgelöst. Es bleibt zu lernen, wie man den globalen Zustand so verändert, wie er entsteht. Wir gehen zu den zuvor erstellten ./reducers/index.js und erstellen einen Reduzierer für dieses Ereignis:
import { SET_MODE } from '../actions/actionTypes'; export default (state = [], action) => { switch (action.type) { case SET_MODE: { return Object.assign({}, state, { mode: action.mode }); } default: return state } };
Großartig! Wenn Sie nun auf die Schaltfläche klicken, wird ein Ereignis generiert, das den globalen Status ändert, und die Fußzeile, die diese Änderungen erhalten hat, zeichnet die Schaltflächen neu.
OriginalartikelStimmt, unglaublich einfach? Es ist beängstigend, sich vorzustellen, wie viele Programmierer bei React Native-Projekten an Altersschwäche sterben und wie viel Geld für all diese Schande bezahlt wird. Das Ergebnis all dessen ist ein kleines Beispiel, etwas komplizierter als Hello World.
Einmal nach dem Konzertprogramm des Albums "... und Gerechtigkeit für alle" im Jahr 1988 sagte Metallica-Chef James Hetfield: "Das ist so ... aber es ist unmöglich, lebend zu spielen." Nachdem ich den Beispielcode für React Native geschrieben hatte, wurde ich solidarisch mit James - das heißt ... aber es ist unmöglich, lebend zu schreiben!
Und so wird dasselbe mit dem Kivy-Framework gemacht:
from kivy.app import App from kivy.factory import Factory from kivy.lang import Builder Builder.load_string(""" <MyButton@Button>: background_down: 'button_down.png' background_normal: 'button_normal.png' color: 0, 0, 0, 1 bold: True on_press: self.parent.parent.ids.textEdit.text = self.text; \ self.color = [.10980392156862745, .5372549019607843, .996078431372549, 1] on_release: self.color = [0, 0, 0, 1] <MyActivity@BoxLayout>: orientation: 'vertical' TextInput: id: textEdit BoxLayout: size_hint_y: None height: dp(45) MyButton: text: '' MyButton: text: '' """) class Program(App): def build(self): my_activity = Factory.MyActivity() return my_activity Program().run()
Es ist so einfach, dass hier sogar Kommentare überflüssig sind.
Ja, Sie haben vielleicht nichts davon gewusst, aber es ist alles in Kivy geschrieben:
vimeo.com/29348760vimeo.com/206290310vimeo.com/25680681www.youtube.com/watch?v=u4NRu7mBXtAwww.youtube.com/watch?v=9rk9OQLSoJwwww.youtube.com/watch?v=aa9LXpg_gd0www.youtube.com/watch?v=FhRXAD8-UkEwww.youtube.com/watch?v=GJ3f88ebDqc&t=111swww.youtube.com/watch?v=D_M1I9GvpYswww.youtube.com/watch?v=VotPQafL7NwAbschließend gebe ich ein Video der Anwendung:
Schreiben Sie in die Kommentare, welche Artikel Sie über Kivy auf den Seiten von Habr sehen möchten. Wenn möglich, werden alle Wünsche erfüllt. Bis bald, dzzya!