简体   繁体   English

LibGDX And​​roid首选项-如何将数据保存到应用

[英]LibGDX Android Preferences - How to save data to app

I've done some research and what I've found is many articles about how to store data like levels, high score, options etc. in an app (LibGDX). 我已经进行了一些研究,发现了很多有关如何在应用程序(LibGDX)中存储数据(例如级别,高分,选项等)的文章。 But none of those solutions have worked for me. 但是这些解决方案都不适合我。 I read the LibGDX description of the class Preferences and searched StackOverflow for an answer of how to use it. 我阅读了Preferences类的LibGDX描述,并在StackOverflow上搜索了如何使用它的答案。

All I want to do is to save the level of the app so the user doesn't have to start over when entering the app again 我要做的就是保存应用程序的级别,以便用户在再次进入应用程序时不必重新开始

Preferences (LibGDX) - https://github.com/libgdx/libgdx/wiki/Preferences 首选项(LibGDX)-https: //github.com/libgdx/libgdx/wiki/Preferences

But when I entered the code that libGDX showed me nothing did happen. 但是当我输入libGDX向我显示的代码时,什么也没发生。 When using Preferences is it enough to enter this code or do I need to do something else too? 使用“首选项”是否足以输入此代码,或者我还需要做其他事情吗?

// LibGDX - create()
Preferences prefs = Gdx.app.getPreferences("My Preferences");

// LibGDX - render()
prefs.putString("name", "Donald Duck");
String name = prefs.getString("name", "No name stored");

prefs.putBoolean("soundOn", true);
prefs.putInteger("highscore", 10);

// bulk update your preferences
prefs.flush();

After that is done I have to get the data that are written to the file called "My Preferences" in this case and get it from there? 完成之后,在这种情况下,我必须获取写入“ My Preferences”文件中的数据,然后从那里获取数据? However, where do I find this file in Android since the LibGDX shows a path for Windows. 但是,由于LibGDX显示了Windows的路径,因此在Android的哪里可以找到此文件。

%UserProfile%/.prefs/My Preferences

By the way, do I need to create a file called "My Preferences" before using this class or will Preferences create the file itself? 顺便说一句,在使用此类之前,我需要创建一个名为“我的首选项”的文件吗?还是由首选项创建文件本身?

Does the code that LibGDX provides be put in a specific order? LibGDX提供的代码是否按特定顺序放置? I know that I have to create a Preferences variable first and then create values to be stored in the file. 我知道我必须先创建一个Preferences变量,然后创建要存储在文件中的值。 But other than that? 但是除此之外呢? Am I using wrong LibGDX methods to put the code into? 我是否使用错误的LibGDX方法将代码放入? (create(), render()). (create(),render())。

I tried to just write the code 我试图写代码

Preferences pref = Gdx.app.getPreferences("somefile");
pref.putString("name", "Menyo");
pref.putInteger("level", 20);

from a link just to see if it worked. 从一个链接,只是看看它是否有效。 But it doesn't seem like it does. 但这似乎并非如此。 Am I missing something in the code? 我在代码中缺少什么吗? Or is it a file I'm missing that needs to be created? 还是需要创建的文件?

Research links: 研究链接:

Using LibGDX with Android Preferences 将LibGDX与Android首选项结合使用

Android libgdx preferences not working Android libgdx首选项不起作用

http://www.badlogicgames.com/forum/viewtopic.php?f=11&t=6365#p32981 http://www.badlogicgames.com/forum/viewtopic.php?f=11&t=6365#p32981

How do apps save data 应用程序如何保存数据

Android libgdx preferences not working Android libgdx首选项不起作用

EDIT: 编辑:

I got an explanation of what I asked but I need more information. 我得到了我所要求的解释,但我需要更多信息。 In my LibGDX app I have the method render and in that method I have menu(), game(). 在我的LibGDX应用程序中,我具有render方法,在该方法中,我具有menu(),game()。 And I am not supposed to call the Preferences in the render method? 我不应该在render方法中调用Preferences吗? Where should them be called since they only will be called once if they're in the create() method right? 应该在哪里调用它们,因为只有在create()方法中它们才被调用一次吗?

I thought that I could call the Preferences (Prefs) methods when exiting the app to save the level data. 我以为我可以在退出应用程序时调用Preferences(Prefs)方法来保存关卡数据。 But it didn't work, the app still goes back to level one. 但这没有用,该应用程序仍返回到第一级。 So I'm deciding to post my code here so that everyone can see what I'm doing wrong. 因此,我决定将代码发布在这里,以便每个人都可以看到我在做错什么。

(You can use CTRL + F to find the variable "level" and "prefs" everywhere in the code) (您可以使用CTRL + F在代码中的所有位置找到变量“ level”和“ prefs”)

package com.game.whatstheanswer;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;

public class MyGdxGame extends ApplicationAdapter implements InputProcessor {
private SpriteBatch batch;

private GAME STATE;
private BitmapFont font;
private BitmapFont fontTextAdjustment;
private GlyphLayout layout;
private String message;

private Texture logo;
private Texture questionmarks;
private Texture keyboard;

private Texture btnReset;
private Texture btnMenu;

@Override
public void create () {
    batch = new SpriteBatch();

    // Configure the Android keyboard
    Gdx.input.setInputProcessor(this);
    isAndroidKeyboardShowing = true;

    // Menu logo
    logo = new Texture("logo.png");
    questionmarks = new Texture("questionmarks.png");
    //level = 1;

    STATE = GAME.MENU;
    btnPlay = new Texture("play.png");
    btnInfo = new Texture("info.png");
    btnQuit = new Texture("quit.png");

    // The game over menu
    btnMenu = new Texture("menu.png");
    btnReset = new Texture("reset.png");

    FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("Amethysta.ttf"));
    FreeTypeFontGenerator.FreeTypeFontParameter parameter = new FreeTypeFontGenerator.FreeTypeFontParameter();
    parameter.size = 66;
    font = generator.generateFont(parameter); // font size 66 pixels

    parameter.size = 28;
    fontTextAdjustment = generator.generateFont(parameter);
    generator.dispose(); // don't forget to dispose to avoid memory leaks!


    layout = new GlyphLayout();

    keyboard = new Texture("keyboard/keyboard.png");

    userInput = "";
    message = "";
    /*
    prefs = Gdx.app.getPreferences("com.game.whatstheanswer.settings");
    String name = prefs.getString("Level", "0");*/

    prefs = new Prefs();
}

private Prefs prefs;
//private static Preferences prefs;

//private String letter;
private String userInput;
private String answer;
private int level;

@Override
public void render () {
    Gdx.gl.glClearColor(255, 255, 255, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    batch.begin();

    //STATE = GAME.OVER;
    switch (STATE) {
        case MENU: menu(); break;
        case PLAY:
            Gdx.gl.glClearColor(0, 0, 0, 1);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
            game();
            break;
        case PAUSE: break;
        case RESUME: break;
        case OVER: gameComplete(); break;
    }
    batch.end();
}

private Texture btnPlay, btnInfo, btnQuit;
private void menu() {
    // Draw a colorful background
    batch.draw(logo, Gdx.graphics.getWidth()/2 - logo.getWidth()/2, Gdx.graphics.getHeight() - logo.getHeight());
    batch.draw(questionmarks, Gdx.graphics.getWidth()/2 - logo.getWidth()/2, Gdx.graphics.getHeight() - logo.getHeight() - 460);
    batch.draw(btnPlay, Gdx.graphics.getWidth() / 2 - btnPlay.getWidth() / 2, 600);
    batch.draw(btnInfo, Gdx.graphics.getWidth() / 2 - btnInfo.getWidth() / 2, 400);
    batch.draw(btnQuit, Gdx.graphics.getWidth() / 2 - btnQuit.getWidth() / 2, 200);

    // Play button
    if (Gdx.input.justTouched() &&
            Gdx.input.getX() >= Gdx.graphics.getWidth()/2 - btnPlay.getWidth()/2 &&
            Gdx.input.getX() <= (Gdx.graphics.getWidth()/2 - btnPlay.getWidth()/2) + btnPlay.getWidth() &&
            Gdx.input.getY() >= Gdx.graphics.getHeight() - 600 - btnPlay.getHeight() &&
            Gdx.input.getY() <= Gdx.graphics.getHeight() - 600) {
        STATE = GAME.PLAY;
        isAndroidKeyboardShowing = true;
        System.out.println("Main Menu: Play");
    }

    // Info button
    if (Gdx.input.justTouched() &&
            Gdx.input.getX() >= Gdx.graphics.getWidth()/2 - btnPlay.getWidth()/2 &&
            Gdx.input.getX() <= (Gdx.graphics.getWidth()/2 - btnPlay.getWidth()/2) + btnPlay.getWidth() &&
            Gdx.input.getY() >= Gdx.graphics.getHeight() - 400 - btnPlay.getHeight() &&
            Gdx.input.getY() <= Gdx.graphics.getHeight() - 400) {
        System.out.println("Main Menu: Info");
    }

    // Quit button
    if (Gdx.input.justTouched() &&
            Gdx.input.getX() >= Gdx.graphics.getWidth()/2 - btnPlay.getWidth()/2 &&
            Gdx.input.getX() <= (Gdx.graphics.getWidth()/2 - btnPlay.getWidth()/2) + btnPlay.getWidth() &&
            Gdx.input.getY() >= Gdx.graphics.getHeight() - 200 - btnPlay.getHeight() &&
            Gdx.input.getY() <= Gdx.graphics.getHeight() - 200) {
        level = prefs.getLevel();
        Gdx.app.exit();
        System.out.println("Main Menu: Quit");
    }
}

/*
public void increaseLevel() {
    prefs.putString("Level", String.valueOf(level));
    prefs.flush();
    Gdx.app.log("level", level+"");
}*/

private void game() {
    // Set the color of the text
    font.setColor(Color.WHITE);

    level = prefs.getLevel();
    /*
    font.draw(batch, "MENU", 120, Gdx.graphics.getHeight() - 80);
    font.draw(batch, "CLEAR", 420, Gdx.graphics.getHeight() - 80);
    font.draw(batch, "SOLVE", 740, Gdx.graphics.getHeight() - 80);*/

    font.draw(batch, "MENU", Gdx.graphics.getWidth()/7, Gdx.graphics.getHeight() - 80);
    font.draw(batch, "CLEAR", (((7/2)*Gdx.graphics.getWidth()/7)), Gdx.graphics.getHeight() - 80);
    font.draw(batch, "SOLVE", 5*Gdx.graphics.getWidth()/7, Gdx.graphics.getHeight() - 80);

    System.out.println(userInput);

    layout.setText(font, "Question " + level);
    font.draw(batch, "Question " + level, Gdx.graphics.getWidth()/2 - layout.width/2, 5*Gdx.graphics.getHeight()/6);

    switch (level) {
        default: break;
        // my cases are in here, removed them to shortned the code
        case 1: break;
        case 2: break;
        ...
        case n: break;
    }

    // The user input
    layout.setText(font, userInput);
    font.draw(batch, userInput, Gdx.graphics.getWidth() / 2 - layout.width / 2, 880);

    //layout.setText(font, letter = "Q");

    drawnKeyboard();
    inputKeyboard();

    ShapeRenderer shapeRenderer = new ShapeRenderer();
    shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
    shapeRenderer.setColor(com.badlogic.gdx.graphics.Color.WHITE);

    // Horizontal line, user input above it
    shapeRenderer.rectLine(110, 800, Gdx.graphics.getWidth() - 110, 800, 4); // shapeRenderer.rectLine(120, 800, 940, 800, 4);

    shapeRenderer.end();

}

// Questions on one line
private void msgOneLine(String message, String answer) {
    this.message = message;
    this.answer = answer;
    layout.setText(font, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width/2, 1200);
}

private void msgTwoLines(String msg1, String msg2, String answer) {
    this.message = msg1;
    this.answer = answer;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200);
    message = msg2;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200 - layout.height - 50);
}

private void msgThreeLines(String msg1, String msg2, String msg3, String answer) {
    this.message = msg1;
    this.answer = answer;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200);

    message = msg2;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200 - layout.height - 50);

    message = msg3;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200 - layout.height - 120);
}

private void msgFourLines(String msg1, String msg2, String msg3, String msg4, String answer) {
    this.message = msg1;
    this.answer = answer;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200);

    message = msg2;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200 - layout.height - 50);

    message = msg3;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200 - layout.height - 120);

    message = msg4;
    layout.setText(fontTextAdjustment, message);
    font.draw(batch, message, (Gdx.graphics.getWidth() / 2) - layout.width - 80, 1200 - layout.height - 190);
}

private void drawnKeyboard() {

    batch.draw(keyboard, 440, 400);

}

private boolean isAndroidKeyboardShowing;
private void inputKeyboard() {
    if (Gdx.input.justTouched()) {
        System.out.println("(" + Gdx.input.getX() + ", " + Gdx.input.getY() + ")");

        // ####################### BOTTOM BUTTONS #######################

        // Menu button
        if (Gdx.input.getX() > 76 && Gdx.input.getX() < 350 && Gdx.input.getY() > 40 && Gdx.input.getY() < 116) {
            STATE = GAME.MENU;
            isAndroidKeyboardShowing = false;
        }

        // Clear button
        if (Gdx.input.getX() > 350 && Gdx.input.getX() < 676 && Gdx.input.getY() > 40 && Gdx.input.getY() < 116) {
            userInput = "";
        }

        // Solve button
        if (Gdx.input.getX() > 676 && Gdx.input.getX() < 976 && Gdx.input.getY() > 40 && Gdx.input.getY() < 116) {
            System.out.println("SOLVE");
            solve();
        }

    }

    // Android keyboard
    Gdx.input.setOnscreenKeyboardVisible(isAndroidKeyboardShowing);
}

// The solve algorithm
private void solve() {

    if (userInput.equalsIgnoreCase(answer)) {
        //level++;
        prefs.increaseLevel();
        userInput = "";
    }

    userInput = "";
}

@Override
public boolean keyDown(int keycode) {

    switch (keycode) {
        // Numbers
        case Input.Keys.NUM_0: userInput += '0'; break;
        case Input.Keys.NUM_1: userInput += '1'; break;
        case Input.Keys.NUM_2: userInput += '2'; break;
        case Input.Keys.NUM_3: userInput += '3'; break;
        case Input.Keys.NUM_4: userInput += '4'; break;
        case Input.Keys.NUM_5: userInput += '5'; break;
        case Input.Keys.NUM_6: userInput += '6'; break;
        case Input.Keys.NUM_7: userInput += '7'; break;
        case Input.Keys.NUM_8: userInput += '8'; break;
        case Input.Keys.NUM_9: userInput += '9'; break;

        // All english letters
        case Input.Keys.A: userInput += 'A'; break;
        case Input.Keys.B: userInput += 'B'; break;
        case Input.Keys.C: userInput += 'C'; break;
        case Input.Keys.D: userInput += 'D'; break;
        case Input.Keys.E: userInput += 'E'; break;
        case Input.Keys.F: userInput += 'F'; break;
        case Input.Keys.G: userInput += 'G'; break;
        case Input.Keys.H: userInput += 'H'; break;
        case Input.Keys.I: userInput += 'I'; break;
        case Input.Keys.J: userInput += 'J'; break;
        case Input.Keys.K: userInput += 'K'; break;
        case Input.Keys.L: userInput += 'L'; break;
        case Input.Keys.M: userInput += 'M'; break;
        case Input.Keys.N: userInput += 'N'; break;
        case Input.Keys.O: userInput += 'O'; break;
        case Input.Keys.P: userInput += 'P'; break;
        case Input.Keys.Q: userInput += 'Q'; break;
        case Input.Keys.R: userInput += 'R'; break;
        case Input.Keys.S: userInput += 'S'; break;
        case Input.Keys.T: userInput += 'T'; break;
        case Input.Keys.U: userInput += 'U'; break;
        case Input.Keys.V: userInput += 'V'; break;
        case Input.Keys.W: userInput += 'W'; break;
        case Input.Keys.X: userInput += 'X'; break;
        case Input.Keys.Y: userInput += 'Y'; break;
        case Input.Keys.Z: userInput += 'Z'; break;

        // Special keys
        case Input.Keys.ENTER: solve(); break;

        case Input.Keys.BACKSPACE:
            if (userInput.length() > 0)
                userInput = userInput.substring(0, userInput.length()-1);
            break;
    }

    return true;
}

private void gameComplete() {
    font.setColor(Color.BLACK);
    layout.setText(font, "Play again? Reset?");
    font.draw(batch, "Play again? Reset?", Gdx.graphics.getWidth()/2 - layout.width/2, Gdx.graphics.getHeight() - 400);

    batch.draw(btnMenu, Gdx.graphics.getWidth() / 2 - btnMenu.getWidth() / 2, 600);
    batch.draw(btnReset, Gdx.graphics.getWidth() / 2 - btnReset.getWidth() / 2, 400);
    batch.draw(btnQuit, Gdx.graphics.getWidth() / 2 - btnQuit.getWidth() / 2, 200);

    // Menu button
    if (Gdx.input.justTouched() &&
            Gdx.input.getX() >= Gdx.graphics.getWidth()/2 - btnMenu.getWidth()/2 &&
            Gdx.input.getX() <= (Gdx.graphics.getWidth()/2 - btnMenu.getWidth()/2) + btnMenu.getWidth() &&
            Gdx.input.getY() >= Gdx.graphics.getHeight() - 600 - btnMenu.getHeight() &&
            Gdx.input.getY() <= Gdx.graphics.getHeight() - 600) {
        STATE = GAME.MENU;
        System.out.println("Game Over Menu: Menu");
    }

    // Reset button
    if (Gdx.input.justTouched() &&
            Gdx.input.getX() >= Gdx.graphics.getWidth()/2 - btnReset.getWidth()/2 &&
            Gdx.input.getX() <= (Gdx.graphics.getWidth()/2 - btnReset.getWidth()/2) + btnReset.getWidth() &&
            Gdx.input.getY() >= Gdx.graphics.getHeight() - 400 - btnReset.getHeight() &&
            Gdx.input.getY() <= Gdx.graphics.getHeight() - 400) {
        level = 1;
        System.out.println("Game Over Menu: Reset");
    }

    // Quit button
    if (Gdx.input.justTouched() &&
            Gdx.input.getX() >= Gdx.graphics.getWidth()/2 - btnQuit.getWidth()/2 &&
            Gdx.input.getX() <= (Gdx.graphics.getWidth()/2 - btnQuit.getWidth()/2) + btnQuit.getWidth() &&
            Gdx.input.getY() >= Gdx.graphics.getHeight() - 200 - btnQuit.getHeight() &&
            Gdx.input.getY() <= Gdx.graphics.getHeight() - 200) {
        Gdx.app.exit();
        System.out.println("Game Over Menu: Quit");
    }
}

// Usual input
@Override
public boolean keyUp(int keycode) {
    return false;
}

@Override
public boolean keyTyped(char character) {
    return false;
}

@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
    return false;
}

@Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
    return false;
}

@Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
    return false;
}

@Override
public boolean mouseMoved(int screenX, int screenY) {
    return false;
}

@Override
public boolean scrolled(int amount) {
    return false;
}

} }

EDIT Sorry man, I have tried to implement the code you gave me but am still doing something wrong. 编辑对不起的人,我曾经试图实施你给我,但我还是做错事的代码。 All of my methods game(), menu(), gameComplete() are in the render() method between batch.begin() and batch.end(). 我所有的方法game(),menu(),gameComplete()都位于batch.begin()和batch.end()之间的render()方法中。 I need to save the level when closing the app. 关闭应用程序时,我需要保存级别。

But how can I check that something actually happened so that I know that my preferences are created? 但是,如何检查实际发生的事情,以便使我知道自己的偏好已创建? Besides, if isn't recommended to call preferences in the render() method where should I call them? 此外,如果不建议在render()方法中调用首选项,应该在哪里调用它们? The create() method only calls preferences once and will therefore not save the level. create()方法仅调用一次首选项,因此不会保存级别。

No need to create My Preferences , it create itself. 无需创建My Preferences ,它可以自行创建。 Preference related work not should be done in render method. 与首选项相关的工作不应在render方法中完成。

On Android, the system's [SharedPreferences][1] class is used. 在Android上,使用系统的[SharedPreferences] [1]类。 This means preferences will survive app updates, but are deleted when the app is uninstalled. 这意味着首选项将在应用程序更新后保留下来,但在卸载应用程序时将被删除。 SharedPreferences store private primitive data in key-value pairs. SharedPreferences将私有原始数据存储在键值对中。

public class TestGame2 extends Game {

    public Prefs prefs;

    @Override
    public void create() {

        prefs=new Prefs();

        System.out.printf("Current Sound Status"+prefs.hasSound());

        // I need to to change sound Status
        prefs.setSound(false);

        //Now sound is off

    }

    public void playSound(){

        if(prefs.hasSound()) {
            Sound sound=Gdx.audio.newSound("....");
            sound.play();
        }
    }

    public void startGame(){

        //what is my previous highest saved game level, last time

        int level=prefs.getLevel();
    }

    public void levelCompleted(){

        // wow last Level completed so now i need to increase level
        prefs.increaseLevel();
    }
}

And Prefs is : Prefs是:

public class Prefs {

    private Preferences pref ;
    private boolean hasSound;
    private int completedLevel;

    public Prefs(){
        pref = Gdx.app.getPreferences("My Preferences");
        hasSound = pref.getBoolean("hasSound",true);
        completedLevel=pref.getInteger("level",0);

    }

    public void setSound(boolean hasSound){
        this.hasSound=hasSound;
        pref.putBoolean("hasSound",hasSound);
        pref.flush();
    }

    public boolean hasSound(){
        return hasSound;
    }

    //should be called once when we need to increase my level
    public void increaseLevel(){
        completedLevel++;
        pref.putInteger("level",completedLevel);
        pref.flush();
    }

    public int getLevel(){
        return completedLevel;
    }
}

EDIT 编辑

Use a counter for the same. 使用相同的计数器。

boolean isDataSaved;

private void gameComplete() {   // your method

  if(!isDataSaved){
      levelCompleted();
      isDataSaved=true;
  }

  font.setColor(Color.BLACK);
  layout.setText(font, "Play again? Reset?");
}

private void menu() {
    isDataSaved=false;
}

And you're already using code of startGame() in your game method. 并且您已经在game方法中使用了startGame()代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM