Android Effizientes Laden von Dateien

Faberix

Mitglied
Hallo Forum

Ich habe ein Jump and Run in Java programmiert, das ich jetzt für Android umschreibe.
Ich bin fast fertig, allerdings gibt es noch ein Problem beim Laden der Levels: Es dauert viel zu lange. Auf der PC-Version dauert es nur wenige Sekunden, auf Android hingegen mehrere Minuten.

Die Levels sind folgendermassen aufgebaut:
Der Spieler läuft über Balken, bei denen ich die oberste und die unterste Pixelreihe speichere. Ausserdem gibt es noch Monster, Münzen und Power-Ups, die ich als Objekte bezeichnen werde (wie die Pixel der Pixelreihen auch). Jedes dieser Objekte hat eine ID, eine XY-Koordinate und eventuell zusätzliche Parameter.

Der Code zum speichern (im Leveleditor verwende ich BufferedImages um das Level zu repräsentieren):
Java:
public void saveForMobile(String path) throws IOException {
        File file = new File(path);
        File file2 = new File(path.substring(0, path.lastIndexOf(".")) + "_2" + ".lvl");
        System.out.println(file2.getAbsolutePath());
        File file3 = new File(path.substring(0, path.lastIndexOf(".")) + "_3" + ".lvl");
        if(file.exists()) {
            int option = JOptionPane.showConfirmDialog(levelEditor.frame, new JLabel("Die Datei " + path + "existiert bereits. Soll sie überschrieben werden?"), "Überschreiben?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
            if(option == JOptionPane.NO_OPTION)
                return;
            file.delete();
        }
        if(file2.exists()) {
            int option = JOptionPane.showConfirmDialog(levelEditor.frame, new JLabel("Die Datei " + file2.getAbsolutePath() + "existiert bereits. Soll sie überschrieben werden?"), "Überschreiben?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
            if(option == JOptionPane.NO_OPTION)
                return;
            file2.delete();
        }
        if(file3.exists()) {
            int option = JOptionPane.showConfirmDialog(levelEditor.frame, new JLabel("Die Datei " + file3.getAbsolutePath() + "existiert bereits. Soll sie überschrieben werden?"), "Überschreiben?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
            if(option == JOptionPane.NO_OPTION)
                return;
            file3.delete();
        }
        file.createNewFile();
        file2.createNewFile();
        file3.createNewFile();
        levelImageGraphics.dispose();
        FileOutputStream outputStream = new FileOutputStream(file);
        FileOutputStream outputStream2 = new FileOutputStream(file2);
        FileOutputStream outputStream3 = new FileOutputStream(file3);
//        Länge und Breite
        outputStream.write(0);
        outputStream.write(toBytes(levelWidth));
        outputStream.write(toBytes(levelHeight));
       
        outputStream2.write(0);
        outputStream2.write(toBytes(levelWidth/2));
        outputStream2.write(toBytes(levelHeight/2));
       
        outputStream3.write(0);
        outputStream3.write(toBytes(levelWidth/4));
        outputStream3.write(toBytes(levelHeight/4));
       
        for(int x = 0; x < levelImage.getWidth(); x++) {
            boolean isBalk = false;
            for(int y = 0; y < levelImage.getHeight(); y++) {
                int rgb = levelImage.getRGB(x, y);
                int id = 0;
                int[] customs = new int[0];
//                Solid
                if(rgb == -16777216) {
                    if(!isBalk) {
                        id = 1;
                        isBalk = true;
                    }
                }
                else {
                    if(isBalk) {
                        id = 2;
                        isBalk = false;
                    }
//                    Sitzender Hund
                    if(rgb == -10485747) {
                        id = 3;
//                        continue;
                    }
//                    Grünes Monster
                    else if(rgb == -16724166) {
                        id = 4;
                    }
//                    Münze
                    else if(rgb == -256) {
                        id = 5;
                    }
//                    Gras
                    else if(rgb == -16724167) {
                        id = 6;
                    }
//                    Steinblock 0
                    else if(rgb == -16724168) {
                        id = 7;
                    }
//                    Steinblock 1
                    else if(rgb == -16724169) {
                        id = 8;
                    }
//                    Steinblock 2
                    else if(rgb == -16724170) {
                        id = 9;
                    }
//                    Steinblock 3
                    else if(rgb == -16724171) {
                        id = 10;
                    }
//                    Flugplattform
                    else if(rgb == FLY_PLATFORM_COLOR.getRGB()) {
                        int rangeUp = 0, rangeDown = 0, rangeLeft = 0, rangeRight = 0;
                        Rectangle entityRect = new Rectangle(x, y, flyPlatformImage.getWidth(), flyPlatformImage.getHeight());
                        for(List<Object> list : entities) {
                            if(entityRect.equals(list.get(INDEX_RECTANGLE))) {
                                RangeButtonListener[] rangeButtonListeners = (RangeButtonListener[]) list.get(INDEX_CUSTOMS);
                                for(RangeButtonListener rangeButtonListener : rangeButtonListeners) {
                                    if(rangeButtonListener.direction == RangeButtonListener.UP) {
                                        rangeUp = Integer.parseInt(rangeButtonListener.getButton().getText());
                                    }
                                    else if(rangeButtonListener.direction == RangeButtonListener.DOWN) {
                                        rangeDown = Integer.parseInt(rangeButtonListener.getButton().getText());
                                    }
                                    else if(rangeButtonListener.direction == RangeButtonListener.LEFT) {
                                        rangeLeft = Integer.parseInt(rangeButtonListener.getButton().getToolTipText());
                                    }
                                    else if(rangeButtonListener.direction == RangeButtonListener.RIGHT) {
                                        rangeRight = Integer.parseInt(rangeButtonListener.getButton().getToolTipText());
                                    }
                                }
                                break;
                            }
                        }
                        id = 11;
                        customs = new int[4];
                        customs[0] = rangeUp;
                        customs[1] = rangeDown;
                        customs[2] = rangeLeft;
                        customs[3] = rangeRight;
//                        continue;
                    }
//                    Schleimmonster
                    else if(rgb == -16724172) {
                        id = 12;
                    }
//                    Schild
                    else if(rgb == -16724173) {
                        id = 13;
                    }
//                    Jetpack
                    else if(rgb == -16724174) {
                        id = 14;
                    }
//                    Magnet
                    else if(rgb == -16724175) {
                        id = 15;
                    }
//                    Flugroboter
                    else if(rgb == -16724176) {
                        int rangeUp = 0, rangeDown = 0, rangeLeft = 0, rangeRight = 0;
                        Rectangle entityRect = new Rectangle(x, y, flyRobotImage.getWidth(), flyRobotImage.getHeight());
                        for(List<Object> list : entities) {
                            if(entityRect.equals(list.get(INDEX_RECTANGLE))) {
                                RangeButtonListener[] rangeButtonListeners = (RangeButtonListener[]) list.get(INDEX_CUSTOMS);
                                for(RangeButtonListener rangeButtonListener : rangeButtonListeners) {
                                    if(rangeButtonListener.direction == RangeButtonListener.UP) {
                                        rangeUp = Integer.parseInt(rangeButtonListener.getButton().getText());
                                    }
                                    else if(rangeButtonListener.direction == RangeButtonListener.DOWN) {
                                        rangeDown = Integer.parseInt(rangeButtonListener.getButton().getText());
                                    }
                                    else if(rangeButtonListener.direction == RangeButtonListener.LEFT) {
                                        rangeLeft = Integer.parseInt(rangeButtonListener.getButton().getToolTipText());
                                    }
                                    else if(rangeButtonListener.direction == RangeButtonListener.RIGHT) {
                                        rangeRight = Integer.parseInt(rangeButtonListener.getButton().getToolTipText());
                                    }
                                }
                                break;
                            }
                        }
                        id = 16;
                        customs = new int[4];
                        customs[0] = rangeUp;
                        customs[1] = rangeDown;
                        customs[2] = rangeLeft;
                        customs[3] = rangeRight;
                    }
                }
                if(id != 0) {
                    outputStream.write(id);
                    outputStream.write(toBytes(x));
                    outputStream.write(toBytes(y));
                    if(customs.length > 0) {
                        for(int i : customs) {
                            outputStream.write(toBytes(i));
                        }
                    }
                    if(id == 1 || id == 2) {
                        if(x%2 == 0) {
                            ByteBuffer.allocate(4);
                            System.out.println("x:" + x/2 + ":" + ByteBuffer.wrap(toBytes(x/2)).getInt());
                            System.out.println("y:" + y/2 + ":" + ByteBuffer.wrap(toBytes(y/2)).getInt());
                            outputStream2.write(id);
                            outputStream2.write(toBytes(x/2));
                            outputStream2.write(toBytes(y/2));
                        }
                        if(x%4 == 0) {
                            outputStream3.write(id);
                            outputStream3.write(toBytes(x/4));
                            outputStream3.write(toBytes(y/4));
                        }
                    }
                    else {
                        outputStream2.write(id);
                        outputStream2.write(toBytes(x/2));
                        outputStream2.write(toBytes(y/2));
                        if(customs.length > 0) {
                            for(int i : customs) {
                                outputStream2.write(toBytes(i/2));
                            }
                        }
                       
                        outputStream3.write(id);
                        outputStream3.write(toBytes(x/4));
                        outputStream3.write(toBytes(y/4));
                        if(customs.length > 0) {
                            for(int i : customs) {
                                outputStream.write(toBytes(i/4));
                            }
                        }
                    }
                   
                }
            }
        }
        outputStream.close();
        outputStream2.close();
        outputStream3.close();
    }

Der Code zum laden:
Java:
/**Lädt ein Level aus einer .lvl-Datei
     * @throws IOException */
    public Level(GameView game, /*URL*/InputStream sourceFile, boolean fromScaledFile) throws IOException {
        int lastX = 0;
        int texturePosX = 0;
        List<Integer> solids = new ArrayList<Integer>();
        List<Rect> preDefinedSolids = new ArrayList<Rect>();
        List<Integer> underSolids = new ArrayList<Integer>();
        sourceFile.read();
        byte[] widthBytes = new byte[4], heightBytes = new byte[4];
        sourceFile.read(widthBytes);
        sourceFile.read(heightBytes);
        ByteBuffer.allocate(4);
        levelWidth = ByteBuffer.wrap(widthBytes).getInt();
        levelHeight = ByteBuffer.wrap(heightBytes).getInt();
        System.out.println("width: " + levelWidth + ", height: " + levelHeight);
       
        while(sourceFile.available() > 0) {
            int id = sourceFile.read();
            byte[] xBytes = new byte[4];
            byte[] yBytes = new byte[4];
            sourceFile.read(xBytes);
            sourceFile.read(yBytes);
            ByteBuffer.allocate(4);
            int x,y;
            if(!fromScaledFile) {
                x = (int)(ByteBuffer.wrap(xBytes).getInt() * GameActivity.scaleFactorX);
                y = (int)(ByteBuffer.wrap(yBytes).getInt() * GameActivity.scaleFactorY);
            }
            else {
                x = ByteBuffer.wrap(xBytes).getInt();
                y = ByteBuffer.wrap(yBytes).getInt();
            }
           
            if(x > lastX) {
                this.solids.put(lastX, solids);
                List<Integer> preSolids = new ArrayList<Integer>();
                for(int i1 = 0; i1 < preDefinedSolids.size(); i1++) {
                    Rect rect = preDefinedSolids.get(i1);
                    preSolids.add(rect.top);
                    if(rect.left + rect.width() < x) {
                        preDefinedSolids.remove(i1);
                        i1--;
                    }
                }
                if(!preSolids.isEmpty())
                    this.preDefinedSolids.put(lastX, preSolids);
                this.underSolids.put(lastX, underSolids);
                lastX = x;
                solids = new ArrayList<Integer>();
                texturePosX++;
                if(texturePosX >= grassTexture.getWidth()) {
                    texturePosX = 0;
                }
            }
            switch(id) {
            case 0:
               
                break;
            case 1:
                solids.add(y);
                System.out.println("solid: " + x + ", " + y);
                break;
            case 2:
                underSolids.add(y);
                break;
            case 3:
                monsters.add(new EntitySittingDog(x, y, game));
                break;
            case 4:
                monsters.add(new EntityGreenMonster(x, y, game));
                break;
            case 5:
                coins.add(new EntityCoin(x, y));
                break;
            case 6:
                drawObjects.add(new DrawObject(x, y, DrawObjectType.GRASS));
                break;
            case 7:
                drawObjects.add(new DrawObject(x, y, DrawObjectType.STONE_BLOCK_0));
                obstacles.add(RectangleFactory.createRect(x, y, (int)(10 * GameActivity.scaleFactorX), (int)(130 * GameActivity.scaleFactorY)));
                preDefinedSolids.add(RectangleFactory.createRect(x, y, (int)(79 * GameActivity.scaleFactorX), (int)(140 * GameActivity.scaleFactorY)));
                break;
            case 8:
                drawObjects.add(new DrawObject(x, y, DrawObjectType.STONE_BLOCK_1));
                obstacles.add(RectangleFactory.createRect(x, y, (int)(79 * GameActivity.scaleFactorX), (int)(130*GameActivity.scaleFactorY)));
                preDefinedSolids.add(RectangleFactory.createRect(x, y, (int)(79*GameActivity.scaleFactorX), (int)(140*GameActivity.scaleFactorY)));
                break;
            case 9:
                drawObjects.add(new DrawObject(x, y, DrawObjectType.STONE_BLOCK_2));
                obstacles.add(RectangleFactory.createRect(x, y, (int)(100*GameActivity.scaleFactorX), (int)(70*GameActivity.scaleFactorY)));
                preDefinedSolids.add(RectangleFactory.createRect(x, y, (int)(100*GameActivity.scaleFactorX), (int)(80*GameActivity.scaleFactorY)));
                break;
            case 10:
                drawObjects.add(new DrawObject(x, y, DrawObjectType.STONE_BLOCK_3));
                obstacles.add(RectangleFactory.createRect(x, y, (int)(100*GameActivity.scaleFactorX), (int)(70*GameActivity.scaleFactorY)));
                preDefinedSolids.add(RectangleFactory.createRect(x, y, (int)(100*GameActivity.scaleFactorX), (int)(80*GameActivity.scaleFactorY)));
                break;
            case 11:
                byte[] upBytes = new byte[4], downBytes = new byte[4], leftBytes = new byte[4], rightBytes = new byte[4];
                sourceFile.read(upBytes);
                sourceFile.read(downBytes);
                sourceFile.read(leftBytes);
                sourceFile.read(rightBytes);
                int upRange = ByteBuffer.wrap(upBytes).getInt();
                int downRange = ByteBuffer.wrap(downBytes).getInt();
                int leftRange = ByteBuffer.wrap(leftBytes).getInt();
                int rightRange = ByteBuffer.wrap(rightBytes).getInt();
               
                movingSolids.add(new EntityFlyPlatform(x, y, x + rightRange, x - leftRange, y + downRange, y - upRange));
                System.out.println("FlyPlatform: x: " + x + ", y: " + y);
                break;
            case 12:
                monsters.add(new EntitySlimeMonster(x, y, game));
                break;
            case 13:
                powerUps.add(new EntityShield(x, y));
                break;
            case 14:
                powerUps.add(new EntityJetpack(x, y));
                break;
            case 15:
                powerUps.add(new EntityMagnet(x, y));
                break;
            case 16:
                byte[] bytesUp = new byte[4], bytesDown = new byte[4], bytesLeft = new byte[4], bytesRight = new byte[4];
                sourceFile.read(bytesUp);
                sourceFile.read(bytesDown);
                sourceFile.read(bytesLeft);
                sourceFile.read(bytesRight);
                upRange = ByteBuffer.wrap(bytesUp).getInt();
                downRange = ByteBuffer.wrap(bytesDown).getInt();
                leftRange = ByteBuffer.wrap(bytesLeft).getInt();
                rightRange = ByteBuffer.wrap(bytesRight).getInt();
               
                monsters.add(new EntityFlyRobot(x, y, x - leftRange, x + rightRange, y - upRange, y + downRange, game));
                break;
                default:
                    break;
            }
        }
        sourceFile.close();
       
       
        finishedLoading = true;
    }

Wie könnte man das effizienter machen?

Ich freue mich auf die Antworten
Mit freundlichen Grüssen, Faberix
 

dzim

Top Contributor
Wow... Ein ganz schöner Brocken!

tl;dr

Hast du denn schon mal einen Debugger angeschlossen und versucht nachzuvollziehen, welcher Code-Teil genau zu lange braucht (im Vergleich zum Desktop)?
Weil: Bis ich den Code durchgearbeitet - und vor allem auch verstanden - habe, vergeht mir jetzt zu viel Zeit :p
 

Dompteur

Top Contributor
So weit ich das überblicke, liest du ziemlich viele kleine Datenblöcke. Da könnte ein BufferedReader die Performance verbessern.
 

Faberix

Mitglied
@dzim: Im unteren Codefenster die while-Schleife. Aber ich vermute vor allem das Lesen geht zu langsam, denn beim grössten teil handelt es sich um ein switch-catch Konstrukt, bei dem die Inhalte nicht so lange dauern sollten.

@Dompteur: Das ist eine gute Idee, aber char ist ein 2-Byte Datentyp. Wie soll ich dann die ID vom Rest trennen?
 
Zuletzt bearbeitet:

dzim

Top Contributor
Oder wenn die Datenstruktur recht fix ist, soll doch wohl das RandomAccesFile recht schnell sein, oder?
 

dzim

Top Contributor
Ich hab das auch mal gelesen und dass man MappedByteBuffer nutzen sollte. Aber: Bei letzterem musst (wenn ich es korrekt verstehe) du Arrays mit der Grösse der Daten anlegen, bei RandomAccessFile "springst" du an die jeweilige Marke deiner Daten.
Wenn du sequentiellen Zugriff brauchst, mag man recht haben und RAF ist nicht die richtige Wahl. Wenn du aber Sprungmarken hast, also auch mal Blöcke überspringen kannst, ist RAF eindeutig immer noch eine durchaus plausible und vor allem Speicherschonende Wahl, denn hier hast du nur den Pointer auf das File - oder besser: in das File. Keine gepufferten/gestreamten Daten.

Ein gutes Beispiel, wo RAF sinnvoll ist, ist der LookupServcie der GeoIP-Datenbanken von Maxmind:
https://github.com/maxmind/geoip-ap...ain/java/com/maxmind/geoip/LookupService.java
Hab selbst damit gearbeitet und mit MultiThreading darauf zugegriffen (durchaus auch mal ein paar dutzend Zugriffe pro Sekunde) und es war stets performant und nie ein Flaschenhals.

Wahrscheinlich kommt es einfach auf den _Einsatzzweck an.
 

Tom299

Bekanntes Mitglied
Ich habs auch nur mal grob überflogen und könnte mir denken, daß es nicht performant ist, immer 4 Bytes zu lesen. Weiß auch nicht, wie so eine Save-Datei im Endeffekt aussieht und wie groß sie ist.
Wäre es nicht möglich, deine "Blöcke" zeilenweise abzuspeichern und darin die verschiedenen Merkmale mit einem Separator zu trennen. Dann könntest du die Datei entweder zeilenweise oder komplett in den Speicher laden und dort dann entsprechend deine Objekte generieren.
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
I Android ListView, Werte aktualisieren ohne die Liste komplett neu zu laden Android & Cross-Platform Mobile Apps 5
W Bild aus dem Internet in View bzw. ImageView laden (Fragment) Android & Cross-Platform Mobile Apps 2
W Firestore String in Apps Laden Android & Cross-Platform Mobile Apps 10
lolcore android studio: fehler bei laden des emulators Android & Cross-Platform Mobile Apps 10
Meeresgott Android Best Practices zum Laden von Dateien Android & Cross-Platform Mobile Apps 0
B Android Textdatei laden (klappt nicht) Android & Cross-Platform Mobile Apps 4
T Android Nach Buttonclick neu laden Android & Cross-Platform Mobile Apps 3
T Hintergrund nur zur hälfte laden... Android & Cross-Platform Mobile Apps 6
N Android Display nicht löschen bzw. neu laden Android & Cross-Platform Mobile Apps 2
D Android Neue Activity im TabWidget laden Android & Cross-Platform Mobile Apps 8
P Android Fragen reihenfolge speichern / laden Android & Cross-Platform Mobile Apps 2
U [Android] Eine Datei aus Jar-Archiv laden Android & Cross-Platform Mobile Apps 4
B Java ME Font laden? Android & Cross-Platform Mobile Apps 6
P dateien laden nur aus jar? Android & Cross-Platform Mobile Apps 7
V Kennt jemand ein Programm , um .DEX Dateien zu öffnen, bearbeiten und wieder speichern? Android & Cross-Platform Mobile Apps 2
J Android String in andere Java-Dateien überführen Android & Cross-Platform Mobile Apps 1
B Dateien Speichern Android & Cross-Platform Mobile Apps 6
T Android Wie kann man bei WebView Dateien hochladen? Android & Cross-Platform Mobile Apps 6
domjos1994 Große Dateien aus Soap-Objekt Android & Cross-Platform Mobile Apps 10
Anfänger2011 Speichern von bereits gefüllten Dateien Android & Cross-Platform Mobile Apps 8
B Android Dateien senden von assets Ordner? Android & Cross-Platform Mobile Apps 7
U MediPlayer: Wave Dateien aus "Music"-Ordner abspielen Android & Cross-Platform Mobile Apps 3
M Dateien in apk z.B. Texturdaten Android & Cross-Platform Mobile Apps 1
G Android Einbindung von externen Dateien in Eclipse Projekt Android & Cross-Platform Mobile Apps 8
@ .jar Dateien auf Android Gerät (Motorola RAZR XT910) Android & Cross-Platform Mobile Apps 6
T Kann man JAD bzw. JAR Dateien auch auf meiner HP öffnen? Android & Cross-Platform Mobile Apps 2
G Auf vorhandene Dateien zugreifen Android & Cross-Platform Mobile Apps 6

Ähnliche Java Themen

Neue Themen


Oben