compréhension de principe de l'accélération matérielle Android du papier blanc

L'accélération matérielle, intuitivement, soit accélération graphique rendu dépendant de l'implémentation GPU La principale différence entre le matériel et le logiciel accélère Rendu graphique GPU pour traiter ce qui est, ou CPU Si un GPU, il est considéré comme un rendu accéléré matériel, alors que le rendu logiciel. La même chose est vraie dans Android, mais par rapport au rendu logiciel moyenne, l'accélération matérielle aussi faire d'autres aspects de l'optimisation, non seulement limitées en termes de dessin, avant de tirer, comment construire sur la zone de dessin, a également fait une grande optimisation de l'accélération matérielle, ainsi les caractéristiques d'accélération matérielle peuvent être analysées à partir des deux parties suivantes:

  • 1, la stratégie initiale: comment construire les besoins de la région à peindre
  • 2, après le tirage: Un fil de rendu séparé, en fonction de la GPU pour le dessin

Que ce soit logiciel ou matériel rendu accéléré, dessin allouer de la mémoire sont similaires, sont la nécessité d'allouer un bloc de demande mémoire services SurfaceFlinger, mais l'accélération matérielle est possible directement à partir de tampons de matériel framebuffer pour allouer de la mémoire (SurfaceFlinger été si sec), deux APP sont tirés de la fin, après avoir dessiné une notification fin SurfaceFlinger doivent également être synthétisés, il n'y a pas de différence dans ce processus, La vraie différence est dans la façon dont les données sont tracés en fin complète UI APP , La compréhension intuitive de ce document sous la différence entre les deux, impliquera une partie du code source, mais sans comprendre.

points de l'accélération matérielle de désaccord

Probablement à partir d'Android 4. + commencer, par défaut, sont pris en charge avec l'accélération matérielle activée, il y a aussi téléphone prend en charge l'accélération matérielle, mais une partie de l'API ne prend pas en charge l'accélération matérielle, si l'utilisation de ces API, vous devez désactiver la principale accélération matérielle ou Afficher l'activité de couche ou couches, comme toile clipPath similaires. Cependant, Voir dessin est un logiciel ou matériel accélèrent à accélérer, généralement pas visible dans le développement de rendu graphique que le temps, les différences entre le matériel et le point de logiciel où est-il? Par exemple, il est nécessaire de redessiner View, appeler généralement vue, invalidate, déclenchement redessinée, suivez cette ligne de go, allez points de contrôle de désaccord.

Voir redessiner

Comme on peut le voir à partir du flux d'appels ci-dessus, et enfin entrer dans la vue de redessiner le tirage ViewRootImpl, il y a un point de décision est le point de l'accélération matérielle de divergence, ce qui suit simplifiée

ViewRootImpl.java

tirage private void (booléen fullRedrawNeeded) { ... if (! dirty.isEmpty () || || mIsAnimating accessibilityFocusDirty) { < ! - accélération clé 1 matériel est activé - > si (mAttachInfo.mHardwareRenderer! = null && mAttachInfo.mHardwareRenderer.isEnabled ()) { ... dirty.setEmpty (); mBlockResizeBuffer = false; < ! - clé 2 accélération matérielle rendu - > mAttachInfo.mHardwareRenderer.draw (MView, mAttachInfo, this); } Else { ... < ! - logiciels clés pour tirer 3 - > if (! drawSoftware (surface, mAttachInfo, xOffset, yOffset, scalingRequired, sale)) { retour; } ...

1 Le point essentiel est de permettre des conditions d'accélération matérielle, et le matériel doit prendre en charge l'accélération matérielle activée avant de pouvoir rencontrer, sur l'utilisation de HardwareRenderer.draw, sinon drawSoftware (logiciel de rendu). Court regard de réponse à cette condition, par défaut, cette condition est vraie, car après 4 + téléphones accélération matérielle de soutien en général, mais aussi en ajoutant la fenêtre lorsque, ViewRootImpl sera l'accélération matérielle ouverte enableHardwareAcceleration, nouvelle HardwareRenderer, et initialise le matériel environnement accéléré.

enableHardwareAcceleration private void (WindowManager.LayoutParams attrs) { < ! - La configuration du commutateur d'accélération matérielle acquise - > // Essayez d'activer l'accélération matérielle sur demande finale = booléen hardwareAccelerated ! (Attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) = 0; si (hardwareAccelerated) { ... < ! - Nouveau matériel graphique accéléré renderer - > mAttachInfo.mHardwareRenderer = HardwareRenderer.create (mContext, translucide); si (mAttachInfo.mHardwareRenderer! = null) { mAttachInfo.mHardwareRenderer.setName (attrs.getTitle () toString ().); mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested = true; } ...

En fait, ici avec des points d'accélération matérielle de rendu logiciel de désaccord ont trouvé, est ViewRootImpl au tirage au sort quand, le cas échéant accélération matérielle sur l'utilisation de HardwareRenderer dessiner, dessiner ou prendre le processus logiciel, drawSoftware est en fait très simple, utilisez Surface.lockCanvas, à SurfaceFlinger demander une allocation de mémoire de mémoire partagée anonyme, en même temps obtenir un SkiaCanvas ordinaire, appelant à des bibliothèques Skia, rendu graphique,

privé drawSoftware booléenne (surface de la surface, AttachInfo AttachInfo, int xoff, int yoff, booléen scalingRequired, sale Rect) { dernière toile en toile; try { < ! - Point clé 1 - > toile = mSurface.lockCanvas (sale); .. < ! - Dessine clé 2 - > mView.draw (toile); .. notification SurfaceFlinger 3 points clés soient des couches synthétisées surface.unlockCanvasAndPost (toile); ...} return true;}

travail drawSoftware entièrement fait par le CPU, le GPU ne concerne pas le fonctionnement, le matériel suivant a porté regard HardwareRenderer réalisé un rendu accéléré.

modèle rendu accéléré du matériel HardwareRenderer

Dit au début, consiste rendu accéléré par le matériel de deux phases: la phase de construction + étape dessin, est de construire le soi-disant traverser récursive toutes les vues, il faudra l'opération en cache, puis de nouveau pour séparer rendu cette discussion Utiliser le rendu OpenGL. l'accélération matérielle Android dans le cadre, la vue est extrait dans RenderNode Voir nud, ce qui rend la vue sera abstraire dans un DrawOp (DisplayListOp), comme dans le drawLine View, la construction sera abstraire en actions DrawLintOp, DrawBitmap peut abstraire en DrawBitmapOp, ce qui rend chaque sous-View est extrait dans DrawRenderNodeOp, chacun a une commande de dessin OpenGL DrawOp correspondant, tout en conservant les données internes nécessaires à l'établissement. Comme suit:

Dessin abstrait Op

Donc, puisque chaque vue non seulement tenir leur propre DrawOp Liste, tout en tenant aussi un enfant de dessin Vue de l'entrée, donc récursion, sera en mesure de compter toute la peinture Op, de nombreux analystes ont appelé Liste d'affichage, le code source est ainsi nommé classe, mais il est en fait plus comme un arbre, plutôt que de liste, donnent les indications suivantes:

accélération matérielle .jpg

Après la construction est terminée, vous pouvez utiliser cet arbre au dessin op fil Render dessiner ici est le même logiciel pour dessiner un endroit très différent lorsque le rendu logiciel, Voir dessin généralement fini dans le thread principal, et l'accélération matérielle, à moins que des exigences particulières, habituellement dessin dans terminez un thread séparé, donc depuis le fil conducteur de partager le sort de la pression pour améliorer la vitesse de réponse du thread d'interface utilisateur.

modèle d'accélération matérielle .jpg

Connaissant tout le modèle, il est un code simple à comprendre le processus de mise en uvre, le regard sous l'arbre et de construire ensemble récursive RenderNode DrawOp.

Construction set DrawOp utilisant HardwareRenderer

ensemble du matériel HardwareRenderer rendu accéléré de l'entrée, est un ThreadedRenderer atteindre l'objet, on peut voir à partir du nom, ThreadedRenderer doit être étroitement liée à un fil de rendu, mais ThreadedRenderer est créé sur le thread d'interface utilisateur, il est également réputé être lié au thread d'interface utilisateur, et son rôle principal :

  • 1, DrawOp complet bâtiment situé dans le thread d'interface
  • 2, responsable de la communication avec le fil de rendu

ThreadedRenderer rôle visible est très important, il suffit de regarder à la mise en uvre:

ThreadedRenderer (contexte de contexte, translucide booléen) { ... < ! - New node-- native > longue rootNodePtr = nCreateRootRenderNode (); mRootNode = RenderNode.adopt (rootNodePtr); mRootNode.setClipToBounds (false); < ! - New NativeProxy-- > mNativeProxy = nCreateProxy (translucide, rootNodePtr); ProcessInitializer.sInstance.init (contexte, mNativeProxy); loadSystemProperties (); }

Vu du code ci-dessus, ThreadedRenderer il y a un ensemble DrawOp RootNode utilisé pour identifier la racine de l'arbre, il y a le nud racine peut accéder à tous dessiner Op, ainsi qu'un objet RenderProxy, qui est maintenant utilisé pour le rendu fil poignée de communication, regardez son constructeur:

RenderProxy :: RenderProxy (bool translucide, RenderNode * rootRenderNode, IContextFactory * ContextFactory) : MRenderThread (RenderThread :: getInstance ()) , MContext (nullptr) { SETUP_TASK (CreateContext); args- > translucide = translucide; args- > rootRenderNode = rootRenderNode; args- > thread = & mRenderThread; args- > ContextFactory = ContextFactory; mContext = (CanvasContext *) postAndWait (tâche); mDrawFrameTask.setContext (& mRenderThread, mContext); }

Comme on peut le voir RenderThread :: getInstance (), RenderThread est un fil singleton, qui est, chaque processus jusqu'à un fil de rendu matériel, donc il n'y aurait pas un conflit d'accès simultanés multi-thread, ici en fait rendu l'environnement matériel il a mis en place un bon environnement. Ici, alors regardez ThreadedRenderer la fonction de tirage au sort, comment construire un arbre de rendu Op:

@Override vide draw (Voir vue, AttachInfo AttachInfo, callbacks HardwareDrawCallbacks) { attachInfo.mIgnoreDirtyState = true; Suggestion finale = attachInfo.mViewRootImpl.mChoreographer; choreographer.mFrameInfo.markDrawStart (); < ! - Légende 1: Vue du bâtiment de l'arbre DrawOp - > updateRootDisplayList (voir, callbacks); < ! - Touche 2: Notification fil RenderThread tirage - > int SyncResult = nSyncAndDrawFrame (mNativeProxy, frameInfo, frameInfo.length); ... }

Seuls les soins sur les points clés 1 updateRootDisplayList, construction RootDisplayList, en fait, la construction Vue de l'arbre DrawOp, updateRootDisplayList à son tour appelle la vue racine de updateDisplayListIfDirty, laissez Voir enfant récursive de updateDisplayListIfDirty, afin de compléter la création d'un arbre DrawOp, processus brièvement:

updateRootDisplayList private void (Voir vue, callbacks HardwareDrawCallbacks) { < ! - Mise à jour - > updateViewTreeDisplayList (vue); si (mRootNodeNeedsUpdate ||! mRootNode.isValid ()) { < ! - Get DisplayListCanvas-- > DisplayListCanvas toile = mRootNode.start (mSurfaceWidth, mSurfaceHeight); try { < ! - utiliser de la toile cache Op-- > saveCount final int = canvas.save (); canvas.translate (mInsetLeft, mInsetTop); callbacks.onHardwarePreDraw (toile); canvas.insertReorderBarrier (); canvas.drawRenderNode (view.updateDisplayListIfDirty ()); canvas.insertInorderBarrier (); callbacks.onHardwarePostDraw (toile); canvas.restoreToCount (saveCount); mRootNodeNeedsUpdate = false; } Enfin { < ! - tous Op rempli RootRenderNode-- > mRootNode.end (toile); } } }
  • Avec Vue de RenderNode get un DisplayListCanvas
  • DisplayListCanvas utiliser pour construire et mettre en cache tous les DrawOp
  • Le cache DisplayListCanvas DrawOp rempli RenderNode
  • Voir le cache racine DrawOp RootRenderNode fourni à la construction complète

Procédé dessin

Regardez simplement la vue DrawOp de construction récursive, et de remplir leur propre

 @NonNull publique RenderNode updateDisplayListIfDirty () { finale RenderNode renderNode = mRenderNode; ... // commencer à acquérir l'accélération matérielle pour dessiner un DisplayListCanvas DisplayListCanvas finale toile = renderNode.start (largeur, hauteur); try { // si textureView couche finale HardwareLayer = getHardwareLayer (); si (couche! = null && layer.isValid ()) { canvas.drawHardwareLayer (couche, 0, 0, mLayerPaint); } Else if (layerType == LAYER_TYPE_SOFTWARE) { // si pour forcer le logiciel à dessiner buildDrawingCache (true); Bitmap cache = getDrawingCache (true); si (cache! = null) { canvas.drawBitmap (cache, 0, 0, mLayerPaint); } } Else { // Si seulement ViewGroup, et d'en tirer leur propre enfant sans récursion directe Voir if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw (toile); } Else { < ! - se disent dessiner, si elle est ViewGroup récursive sous View-- > dessiner (toile); } } } Enfin { < ! - build cache Op-- > renderNode.end (toile); setDisplayListProperties (renderNode); } } retourner renderNode; }

Voir TextureView avec un logiciel spécial obligatoire pour établir des comparaisons, il y a un traitement supplémentaire ici ne se soucient pas, regarder directement le tirage ordinaire, si dans la vue onDraw, il y a un drawLine ici appellera DisplayListCanvas de la fonction drawLine, DisplayListCanvas et diagramme de classes RenderNode au sujet comme suit

l'accélération matérielle de la classe figure

DisplayListCanvas de la fonction drawLine finiront par entrer dans le drawLine DisplayListCanvas.cpp,

DisplayListCanvas vide :: drawLines (float const * points, nombre int, const SkPaint et peinture) { points = refBuffer < flotteur > (Points, compter); addDrawOp (nouveau (alloc ()) DrawLinesOp (points, nombre, refPaint (et peinture))); }

On peut voir ici construit une DrawLinesOp, et ajouté à la liste de cache DisplayListCanvas aller, afin que nous puissions achever la construction d'un arbre récursif DrawOp, l'utilisation RenderNode après la construction de la fonction de fin, les données mises en cache dans DisplayListCanvas RenderNode aller à:

public void fin (toile DisplayListCanvas) { canvas.onPostDraw (); longue renderNodeData = canvas.finishRecording (); < ! - va DrawOp go RenderNode en cache - > nSetDisplayListData (mNativeRenderNode, renderNodeData); // récupération de la toile off> canvas.recycle (); mvalid = true; }

Donc, nous allons terminer la construction de l'arbre DrawOp, après avoir utilisé RenderProxy envoyer un message à RenderThread, demandant fil rendu OpenGL.

RenderThread pour rendre l'interface utilisateur graphique Tampon

Après la construction d'arbres achèvement DrawOp, le thread d'interface utilisateur à l'aide d'un RenderProxy thread envoie une demande de tâche RenderThread DrawFrameTask, RenderThread se réveille, commencer le rendu, le processus général suivant:

  • Tout d'abord, la fusion DrawOp
  • Prochain tirage couche spéciale
  • Enfin, tirer tout le reste DrawOpList
  • Appel SwapBuffers élaboreront un bon tampon graphiques précédemment soumis à la synthèse et l'affichage Surface Flinger.

Mais avant que la brosse en tirant l'origine de la mémoire, construite avant l'arbre après tout DrawOp juste une mémoire utilisateur ordinaire, et une partie des données pour SurfaceFlinger sont invisibles, puis attirés par les données de mémoire partagée seront SurfaceFlinger synthèse, logiciel d'analyse avant de l'interface utilisateur avait tirée d'une mémoire partagée anonyme, de sorte que l'accélération matérielle la mémoire partagée vient? Ici, vous pouvez revenir en arrière et de regarder ViewRootImlp

performTraversals private void () { ... si (mAttachInfo.mHardwareRenderer! = null) { try { hwInitialized = mAttachInfo.mHardwareRenderer.initialize ( mSurface); si (hwInitialized && (host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) { mSurface.allocateBuffers (); } } Catch (e OutOfResourcesException) { handleOutOfResourcesException (e); retour; } } .... / ** * Allouer tampons à l'avance pour éviter les retards d'allocation pendant le rendu * @Hide * / allocateBuffers public void () { synchronisée (mlock) { checkNotReleasedLocked (); nativeAllocateBuffers (mNativeObject); } }

Comme on peut le voir, pour l'accélération matérielle scène, lorsque la possibilité de demander l'allocation de mémoire SurfaceFlinger un peu plus tôt, plutôt que comme logiciel de rendu, initié par la surface de lockCanvas, les principaux objectifs sont les suivants: la position de créneau horaire attribué avant, afin d'éviter une nouvelle demande lors du rendu est d'éviter l'échec d'allocation, une perte de travaux préparatoires avant que le processeur, le second est également possible de rendre fil de travail pour simplifier et réduire le délai. Cependant, il y a encore un autre problème, un processus APP, sur la même période, il y aura une interface graphique de surface, mais seulement un fil de rendu, alors qu'est-ce qui la rendent? Cette fois-ci nous avons besoin de la surface et le fil de rendu (contexte) contraignant.

statique jboolean android_view_ThreadedRenderer_initialize (env JNIEnv *, jobject clazz, jlong proxyPtr, jobject jsurface) { RenderProxy * proxy = reinterpret_cast < RenderProxy * > (ProxyPtr); sp < ANativeWindow >  Fenêtre = android_view_Surface_getNativeWindow (env, jsurface); retour proxy- > initialiser (fenêtre); }

La première surface acquise par android_view_Surface_getNativeWindowSurface, la couche native, surface correspondant à une ANativeWindow, puis, les fonctions membres de ANativeWindow de la classe initialize RenderProxy obtenu précédemment lié à RenderThread

bool RenderProxy :: initialize (const sp < ANativeWindow > Et fenêtre) { SETUP_TASK (initialisation); args- > contexte = mContext; args- > fenêtre = window.get (); retour (bool) postAndWait (tâche); }

envoie toujours un message au fil de rendu, laissez la fenêtre de liaison actuelle, en fait, appelez CanvasContext la fonction initialize, si graphiques contexte lié mémoire graphique:

bool CanvasContext :: initialize (ANativeWindow fenêtre *) { setSurface (fenêtre); si (mCanvas) faux retour; mCanvas = new OpenGLRenderer (mRenderThread.renderState ()); mCanvas- > InitProperties (); return true; }

CanvasContext setSurface par le courant à rendre la surface liée à RenderThread dans le flux général est obtenu par un EGLSurface eglApi, EGLSurface encapsule une surface de dessin, et en outre, par eglApi EGLSurface ensemble rendent la fenêtre, et analogues pour la mémoire graphique courante informations sont synchronisées par le temps RenderThread après le dessin afin de savoir ce qui est dessiné sur la fenêtre. Voici l'accueil principal avec la bibliothèque OpenGL, toutes les opérations seront finalement attribués à l'interface abstraite eglApi aller. Si, ce n'est pas Android, est une plate-forme Java commune, a également besoin d'une opération similaire, le processus d'encapsulation et est tenu de rendre la EGLSurface actuelle, car OpenGL est un ensemble de spécifications, vous souhaitez utiliser, il faut aller conformément à cette spécification. Ensuite, créez un second objet OpenGLRenderer derrière la mise en uvre des opérations liées à l'OpenGL, quand, en fait, réalisé par le OpenGLRenderer.

processus de liaison

Le processus ci-dessus complété, ordonné DrawOp tree've construit, l'allocation de mémoire a également été bonne, l'environnement et les scènes se lient réussit, le reste est tiré, mais dit, le tirage réel des appels OpenGL avant l'opération de fusion ceci est fait pour optimiser l'accélération matérielle Android, tourner autour et continuer à faire avancer le processus de tirage au sort, en effet, aller drawRenderNode OpenGLRenderer un processus récursif:

vide OpenGLRenderer :: drawRenderNode (RenderNode * renderNode, Rect et sale, int32_t replayFlags) { ... < ! - Construction de deferredList-- > DeferredDisplayList deferredList (mState.currentClipRect (), avoidOverdraw); DeferStateStruct deferStruct (deferredList, * ce, replayFlags); < ! - Fusions et groupes - > renderNode- > defer (deferStruct, 0); < ! - Dessine layer-- > flushLayers (); startFrame (); < ! - Dessine arbre DrawOp - > deferredList.flush (* cela, sale); ... }

Accélération matérielle processus de rendu

Regardez renderNode- > Différer (deferStruct, 0), l'opération de fusion, arbre DrawOp est pas directement tiré, mais d'abord procédé à une optimisation combinée DeferredDisplayList, ceci est une optimisation des moyens d'accélération matérielle Android utilisés, non seulement peut réduire le dessin inutile, il peut également être centralisée traitement de dessin similaire pour améliorer la vitesse de dessin.

vide RenderNode :: defer (DeferStateStruct & deferStruct, niveau const int) { gestionnaire DeferOperationHandler (deferStruct, niveau); issueOperations < DeferOperationHandler > (DeferStruct.mRenderer, gestionnaire); }

RenderNode :: defer contient en fait une opération récursive, par exemple, si les représentants du RenderNode actuel DecorView, il va récursive fusionner toutes l'optimisation sous-View et processus brièvement algorithme de fusion et d'optimisation, en fait, principalement pour construire DeferedDisplayList selon l'arbre DrawOp , Defer, il y a eu des retards moyenne, pour la fusion DrawOp de deux conditions nécessaires,

  • 1: deux DrawOp doit être le même type, lorsque ce type de fusion sont abstraire comme ID de lot, les valeurs sont les suivantes:
 ENUM OpBatchId { kOpBatch_None = 0, // Do not lot kOpBatch_Bitmap, kOpBatch_Patch, kOpBatch_AlphaVertices, kOpBatch_Vertices, kOpBatch_AlphaMaskTexture, kOpBatch_Text, kOpBatch_ColorText, kOpBatch_Count, // Ajouter d'autres ids de commandes avant cette };
  • 2: DrawOp d'ID de fusion doit être le même, ID de fusion pas trop restrictive, personnalisée par chaque décision de DrawOp, mais il semble que DrawPatchOp, DrawBitmapOp, spécial DrawTextOp, et le reste ne pas besoin de tenir compte de la fusion semble, est au-dessus de trois instant, conditions de fusion sont très dures

Dans le processus de fusion, DrawOp être divisé en deux types: la nécessité et ne pas besoin de fusionner ensemble et sont mises en cache dans les différentes listes, vous ne pouvez pas fusionner par type ont été stockés dans le lot * mBatchLookup Il peut être combiné en fonction du type et MergeID stocké TinyHashMap < mergeid_t, DrawBatch * >  mMergingBatches Le diagramme ci-dessous:

Les opérations de fusion de DrawOp

Après la fusion, DeferredDisplayList Vector < lot * >  mBatches y compris une commande de dessin de l'ensemble intégré, après le rendu peut être, il faut noter que la fusion ne soit pas plus qu'un changement ici, faire juste une collection, principalement pour faciliter l'utilisation de diverses textures et d'autres ressources, telles que le temps pour dessiner le texte, vous devez selon le texte du rendu de la texture, mais cette fois vous avez besoin de coordonnées de texture de texte de la requête, ont fusionné ensemble pour faciliter le traitement unifié, une fois rendu, réduire les déchets de chargement des ressources, bien sûr, pour la compréhension du processus global de l'accélération matérielle, l'opération de fusion peut être complètement ignoré même penser intuitivement, après que vous avez construit, vous pouvez diriger le rendu, sa principale caractéristique est Render pour dessiner un autre thread en utilisant OpenGL, c'est la plus caractéristique importante . Tous les mBatches DrawOp sera tiré au sort dans le GraphicBuffer par OpenGL, et enfin la synthèse par notification SwapBuffers SurfaceFlinger.

résumé

Accélération matérielle logiciel de rendu avec la principale différence réside dans le tirage, l'allocation de mémoire globale du procédé, la synthèse de la couche est le même, mais par rapport à accélération matérielle de rendu algorithme logiciel est plus raisonnable, tout en utilisant un fil de rendu séparé, ce qui réduit le fil principal fardeau.

Pour référence, s'il vous plaît me corriger

[joint] Vidéos liées à l'architecture

collecte des données

Regarder + réponse privée lettre, libre accès « données Andrews »!

Android pour obtenir à recevoir l'architecture avancée de l'information, le code source, notes, vidéo. Interface utilisateur principale, optimisation des performances, des cours d'architecte, NDK, développement hybride (ReactNative + Weex) applet micro lettre, pratique avancée Flutter Android tous les aspects de la technologie

Sun Li a envoyé un utilisateur de micro-blogging de l'aide, mais était misérable malédiction
Précédent
Recommandé cinq navires de guerre en mer super grand, si vous vous sentez navires armés dans la bataille de la mer!
Prochain
module de commande de lecture: la plus lourde artillerie de beau haut, EW Slugger
Consommateurs drones conception du système de transmission d'images de quelques-uns de la clé | dur pour créer la classe ouverte
Xu Zheng femme mystérieuse a été abattu et sa femme la nuit TAO avait dit: physiquement peu importe
module de commande de lecture: MG de la vieille Albion
Jeep distributeurs automatiques jouer, la vente de la « fête cent insectes »
Recommandé cinq spoof hilarante du film, les gens rient éclatés
« Petite sorcière Académie, » les dernières informations: Professeur complet débuts
module de commande de lecture: HG ancienne équipe de 08ms
MVC, MVP, MVVM, comment choisir?
Contrôle de lecture du mode: GK Mercury & Waye Te
Leslie a dit: « Je suis des publicités, plus de choisir un film. »
Français joué film de Hou Hsiao-hsien tombe et Gong Li, ancien agent depuis 35 ans l'amour du même sexe est trop triste