簡體   English   中英

AsyncTask API 在 Android 11 中被棄用。有哪些替代方案?

[英]The AsyncTask API is deprecated in Android 11. What are the alternatives?

谷歌在 Android 11 中棄用了 Android AsyncTask API,並建議改用java.util.concurrent 你可以在這里查看提交

 *
 * @deprecated Use the standard <code>java.util.concurrent</code> or
 *   <a href="https://developer.android.com/topic/libraries/architecture/coroutines">
 *   Kotlin concurrency utilities</a> instead.
 */
@Deprecated
public abstract class AsyncTask<Params, Progress, Result> {

如果您在 Android 中維護一個帶有異步任務的舊代碼庫,您將來可能不得不更改它。 我的問題是應該使用java.util.concurrent正確替換下面顯示的代碼片段。 它是 Activity 的靜態內部類。 我正在尋找適用於minSdkVersion 16的東西

private static class LongRunningTask extends AsyncTask<String, Void, MyPojo> {
        private static final String TAG = MyActivity.LongRunningTask.class.getSimpleName();
        private WeakReference<MyActivity> activityReference;

        LongRunningTask(MyActivity context) {
            activityReference = new WeakReference<>(context);
        }

        @Override
        protected MyPojo doInBackground(String... params) {
            // Some long running task
            
        }

        @Override
        protected void onPostExecute(MyPojo data) {

            MyActivity activity = activityReference.get();
            activity.progressBar.setVisibility(View.GONE);
            populateData(activity, data) ;
        }     


    }

您可以直接使用java.util.concurrent包中的Executors

我還搜索了它,並在這個Android Async API is Deprecated帖子中找到了解決方案。

不幸的是,該帖子使用的是 Kotlin,但經過一些努力我將其轉換為 Java。 所以這是解決方案。

ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());

executor.execute(new Runnable() {
    @Override
    public void run() {

        //Background work here

        handler.post(new Runnable() {
            @Override
            public void run() {
                //UI Thread work here
            }
        });
    }
});

很簡單吧? 如果您在項目中使用 Java 8,則可以稍微簡化它。

ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());

executor.execute(() -> {
    //Background work here
    handler.post(() -> {
        //UI Thread work here
    });
});

盡管如此,它在代碼的簡潔性方面仍然無法擊敗 kotlin,但比以前的 java 版本要好。

希望這會幫助你。 謝謝你

 private WeakReference<MyActivity> activityReference;

很好的擺脫它已被棄用, 因為WeakReference<Context>總是一個黑客,而不是一個適當的解決方案

現在人們將有機會凈化他們的代碼。


 AsyncTask<String, Void, MyPojo>

根據這段代碼,其實不需要Progress ,有一個String輸入+ MyPojo輸出。

這實際上很容易在不使用 AsyncTask 的情況下完成。

public class TaskRunner {
    private final Executor executor = Executors.newSingleThreadExecutor(); // change according to your requirements
    private final Handler handler = new Handler(Looper.getMainLooper());

    public interface Callback<R> {
        void onComplete(R result);
    }

    public <R> void executeAsync(Callable<R> callable, Callback<R> callback) {
        executor.execute(() -> {
            final R result = callable.call();
            handler.post(() -> {
                callback.onComplete(result);
            });
        });
    }
}

如何傳入字符串? 像這樣:

class LongRunningTask implements Callable<MyPojo> {
    private final String input;

    public LongRunningTask(String input) {
        this.input = input;
    }

    @Override
    public MyPojo call() {
        // Some long running task
        return myPojo;
    }
}

// in ViewModel
taskRunner.executeAsync(new LongRunningTask(input), (data) -> {
    // MyActivity activity = activityReference.get();
    // activity.progressBar.setVisibility(View.GONE);
    // populateData(activity, data) ;

    loadingLiveData.setValue(false);
    dataLiveData.setValue(data);
});

// in Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main_activity);

    viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    viewModel.loadingLiveData.observe(this, (loading) -> {
        if(loading) {
            progressBar.setVisibility(View.VISIBLE);
        } else {
            progressBar.setVisibility(View.GONE);
        }
    });

    viewModel.dataLiveData.observe(this, (data) -> {
        populateData(data);
    }); 
}

這個例子使用了一個適合數據庫寫入(或序列化網絡請求)的單線程池,但是如果你想要數據庫讀取或多個請求的東西,你可以考慮以下執行器配置:

private static final Executor THREAD_POOL_EXECUTOR =
        new ThreadPoolExecutor(5, 128, 1,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

最簡單的替代方法之一是使用Thread

new Thread(new Runnable() {
    @Override
    public void run() {
        // do your stuff
        runOnUiThread(new Runnable() {
            public void run() {
                // do onPostExecute stuff
            }
        });
    }
}).start();

如果你的項目支持JAVA 8 ,你可以使用lambda

new Thread(() -> {
    // do background stuff here
    runOnUiThread(()->{
        // OnPostExecute stuff here
    });
}).start();

根據Android 文檔AsyncTaskAPI 級別 30中被棄用,建議改用標准的 java.util.concurrent 或Kotlin 並發實用程序

使用后者可以非常簡單地實現:

  1. CoroutineScope上創建通用擴展函數:

     fun <R> CoroutineScope.executeAsyncTask( onPreExecute: () -> Unit, doInBackground: () -> R, onPostExecute: (R) -> Unit ) = launch { onPreExecute() // runs in Main Thread val result = withContext(Dispatchers.IO) { doInBackground() // runs in background thread without blocking the Main Thread } onPostExecute(result) // runs in Main Thread }
  2. 將函數與任何具有Dispatchers.Main上下文的CoroutineScope一起使用:

    • ViewModel中:

       class MyViewModel: ViewModel() { fun someFun() { viewModelScope.executeAsyncTask(onPreExecute = { //... runs in Main Thread }, doInBackground = { //... runs in Worker(Background) Thread "Result" // send data to "onPostExecute" }, onPostExecute = { // runs in Main Thread //... here "it" is the data returned from "doInBackground" }) } }
    • ActivityFragment中:

       lifecycleScope.executeAsyncTask(onPreExecute = { //... runs in Main Thread }, doInBackground = { //... runs in Worker(Background) Thread "Result" // send data to "onPostExecute" }, onPostExecute = { // runs in Main Thread //... here "it" is the data returned from "doInBackground" })

    要使用viewModelScopelifecycleScope ,請將下一行添加到應用程序的build.gradle文件的依賴項中:

     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope

    在撰寫本文時final LIFECYCLE_VERSION = "2.3.0-alpha05"

更新

我們也可以使用onProgressUpdate函數實現進度更新:

fun <P, R> CoroutineScope.executeAsyncTask(
        onPreExecute: () -> Unit,
        doInBackground: suspend (suspend (P) -> Unit) -> R,
        onPostExecute: (R) -> Unit,
        onProgressUpdate: (P) -> Unit
) = launch {
    onPreExecute()

    val result = withContext(Dispatchers.IO) {
        doInBackground {
            withContext(Dispatchers.Main) { onProgressUpdate(it) }
        }
    }
    onPostExecute(result)
}

將任何CoroutineScopeviewModelScope / lifecycleScope ,參見上面的實現)與Dispatchers.Main上下文一起使用,我們可以調用它:

someScope.executeAsyncTask(
    onPreExecute = {
        // ... runs in Main Thread
    }, doInBackground = { publishProgress: suspend (progress: Int) -> Unit ->
        
        // ... runs in Background Thread

        // simulate progress update
        publishProgress(50) // call `publishProgress` to update progress, `onProgressUpdate` will be called
        delay(1000)
        publishProgress(100)

        
        "Result" // send data to "onPostExecute"
    }, onPostExecute = {
        // runs in Main Thread
        // ... here "it" is a data returned from "doInBackground"
    }, onProgressUpdate = {
        // runs in Main Thread
        // ... here "it" contains progress
    }
)

使用此類在后台線程中執行后台任務此類適用於所有 Android API 版本,包括 Android 11此代碼與AsyncTask一樣,具有doInBackgroundonPostExecute方法

public abstract class BackgroundTask {

    private Activity activity;
    public BackgroundTask(Activity activity) {
        this.activity = activity;
    }

    private void startBackground() {
        new Thread(new Runnable() {
            public void run() {

                doInBackground();
                activity.runOnUiThread(new Runnable() {
                    public void run() {

                        onPostExecute();
                    }
                });
            }
        }).start();
    }
    public void execute(){
        startBackground();
    }

    public abstract void doInBackground();
    public abstract void onPostExecute();

}

復制上面的類后,你就可以使用它了:

new BackgroundTask(MainActivity.this) {
        @Override
        public void doInBackground() {

            //put you background code
            //same like doingBackground
            //Background Thread
        }

        @Override
        public void onPostExecute() {

            //hear is result part same
            //same like post execute
            //UI Thread(update your UI widget)
        }
    }.execute();

Android 在 Android 11 中棄用了AsyncTask API,以擺脫一開始的一些問題。

那么,現在怎么樣了?

  • 線程
  • 執行者
  • RxJava
  • 可聽期貨
  • 協程

為什么要協程?

協程是 Kotlin 進行異步編程的方式。 自 Kotlin 1.3 以來編譯器支持穩定,連同kotlinx.coroutines庫 -

  • 結構化並發
  • 非阻塞、順序代碼
  • 取消傳播
  • 自然異常處理

在這里,我使用協程為 AsyncTask 創建了一個替代方案,它可以與 AsyncTask 一樣使用,而無需更改項目中的太多代碼庫。

  1. 創建一個新的抽象類 AsyncTaskCoroutine,它接受輸入參數和輸出參數數據類型——當然這些參數是可選的:)

     import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async import kotlinx.coroutines.launch abstract class AsyncTaskCoroutine<I, O> { var result: O? = null //private var result: O open fun onPreExecute() {} open fun onPostExecute(result: O?) {} abstract fun doInBackground(vararg params: I): O fun <T> execute(vararg input: I) { GlobalScope.launch(Dispatchers.Main) { onPreExecute() callAsync(*input) } } private suspend fun callAsync(vararg input: I) { GlobalScope.async(Dispatchers.IO) { result = doInBackground(*input) }.await() GlobalScope.launch(Dispatchers.Main) { onPostExecute(result) } } }

2. 現在在 Activity 中像使用舊的 AsycnTask 一樣使用它

 new AsyncTaskCoroutine() {
                @Override
                public Object doInBackground(Object[] params) {
                    return null;
                }
    
                @Override
                public void onPostExecute(@Nullable Object result) {
    
                }
    
                @Override
                public void onPreExecute() {
    
                }
            }.execute();
  1. 如果您需要發送傳遞參數,以防萬一

     new AsyncTaskCoroutine<Integer, Boolean>() { @Override public Boolean doInBackground(Integer... params) { return null; } @Override public void onPostExecute(@Nullable Boolean result) { } @Override public void onPreExecute() { } }.execute();

Google 建議使用 Java 的並發框架或 Kotlin Coroutines。 但是 Rxjava 最終比 Java 並發具有更多的靈活性和特性,因此獲得了相當大的普及。

我實際上寫了兩個關於它的 Medium 故事:

第一個是使用 Java 和 Runnable 的變通方法,第二個是 Kotlin 和協程解決方案。 當然,兩者都有代碼示例。

在這里,我還使用抽象類為 AsyncTask 創建了一個替代方案,它可以作為一個類進行復制。

/app/src/main/java/../AsyncTasks.java

public abstract class AsyncTasks {
    private final ExecutorService executors;

    public AsyncTasks() {
        this.executors = Executors.newSingleThreadExecutor();
    }

    private void startBackground() {
        onPreExecute();
        executors.execute(new Runnable() {
            @Override
            public void run() {
                doInBackground();
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        onPostExecute();
                    }
                });
            }
        });
    }

    public void execute() {
        startBackground();
    }

    public void shutdown() {
        executors.shutdown();
    }

    public boolean isShutdown() {
        return executors.isShutdown();
    }

    public abstract void onPreExecute();

    public abstract void doInBackground();

    public abstract void onPostExecute();
}

上述類的實現/使用

new AsyncTasks() {
     @Override
            public void onPreExecute() { 
             // before execution
      }

     @Override
            public void doInBackground() {
              // background task here 
            }

     @Override
            public void onPostExecute() { 
             // Ui task here
            }
     }.execute();

我的自定義替換: https ://github.com/JohnyDaDeveloper/AndroidAsync

它僅在應用程序運行時起作用(更具體地說是計划任務的活動),但它能夠在后台任務完成后更新 UI

編輯:我的 AsyncTask 不再需要 Activiy 才能運行。

把整個類換成這個Thread,放在一個方法里傳遞變量就行了

new Thread(() -> {
            // do background stuff here
            runOnUiThread(()->{
                // OnPostExecute stuff here
              
            });
        }).start();

並在 Fragment 中將 Context 添加到runOnUiThread()方法中:

 new Thread(() -> {
            // do background stuff here
            context.runOnUiThread(()->{
                // OnPostExecute stuff here
            });
        }).start();

接受的答案很好。 但是...我沒有看到 cancel() 方法的實現

所以我有可能取消正在運行的任務(模擬取消)的實現如下。 在任務中斷的情況下,需要取消才能不運行 postExecute() 方法。

public abstract class AsyncTaskExecutor<Params> {
    public static final String TAG = "AsyncTaskRunner";

    private static final Executor THREAD_POOL_EXECUTOR =
            new ThreadPoolExecutor(5, 128, 1,
                    TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

    private final Handler mHandler = new Handler(Looper.getMainLooper());
    private boolean mIsInterrupted = false;

    protected void onPreExecute(){}
    protected abstract Void doInBackground(Params... params);
    protected void onPostExecute(){}
    protected void onCancelled() {}

    @SafeVarargs
    public final void executeAsync(Params... params) {
        THREAD_POOL_EXECUTOR.execute(() -> {
            try {
                checkInterrupted();
                mHandler.post(this::onPreExecute);

                checkInterrupted();
                doInBackground(params);

                checkInterrupted();
                mHandler.post(this::onPostExecute);
            } catch (InterruptedException ex) {
                mHandler.post(this::onCancelled);
            } catch (Exception ex) {
                Log.e(TAG, "executeAsync: " + ex.getMessage() + "\n" + Debug.getStackTrace(ex));
            }
        });
    }

    private void checkInterrupted() throws InterruptedException {
        if (isInterrupted()){
            throw new InterruptedException();
        }
    }

    public void cancel(boolean mayInterruptIfRunning){
        setInterrupted(mayInterruptIfRunning);
    }

    public boolean isInterrupted() {
        return mIsInterrupted;
    }

    public void setInterrupted(boolean interrupted) {
        mIsInterrupted = interrupted;
    }
}

使用此類的示例:

public class MySearchTask extends AsyncTaskExecutor<String> {

    public MySearchTask(){
    }

    @Override
    protected Void doInBackground(String... params) {
        // Your long running task
        return null;
    }

    @Override
    protected void onPostExecute() {
        // update UI on task completed
    }

    @Override
    protected void onCancelled() {
        // update UI on task cancelled
    }
}

MySearchTask searchTask = new MySearchTask();
searchTask.executeAsync("Test");

您可以使用此自定義類作為 AsyncTask<> 的替代品,這與 AsyncTask 相同,因此您無需為此付出額外的努力。

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TaskRunner {

    private static final int CORE_THREADS = 3;
    private static final long KEEP_ALIVE_SECONDS = 60L;
    private static TaskRunner taskRunner = null;
    private Handler handler = new Handler(Looper.getMainLooper());
    private ThreadPoolExecutor executor;

    private TaskRunner() {
        executor = newThreadPoolExecutor();
    }

    public static TaskRunner getInstance() {
        if (taskRunner == null) {
            taskRunner = new TaskRunner();
        }
        return taskRunner;
    }

    public void shutdownService() {
        if (executor != null) {
            executor.shutdown();
        }
    }

    public void execute(Runnable command) {
        executor.execute(command);
    }

    public ExecutorService getExecutor() {
        return executor;
    }

    public <R> void executeCallable(@NonNull Callable<R> callable, @NonNull OnCompletedCallback<R> callback) {
        executor.execute(() -> {
            R result = null;
            try {
                result = callable.call();
            } catch (Exception e) {
                e.printStackTrace(); // log this exception
            } finally {
                final R finalResult = result;
                handler.post(() -> callback.onComplete(finalResult));
            }
        });
    }

    private ThreadPoolExecutor newThreadPoolExecutor() {
        return new ThreadPoolExecutor(
                CORE_THREADS,
                Integer.MAX_VALUE,
                KEEP_ALIVE_SECONDS,
                TimeUnit.SECONDS,
                new SynchronousQueue<>()
        );
    }

    public interface OnCompletedCallback<R> {
        void onComplete(@Nullable R result);
    }
}

如何使用它? 請按照以下示例進行操作。

使用 lambda 表達式

TaskRunner.getInstance().executeCallable(() -> 1, result -> {
});


TaskRunner.getInstance().execute(() -> {
});

沒有 lambda 表達式

TaskRunner.getInstance().executeCallable(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        return 1;
    }
}, new TaskRunner.OnCompletedCallback<Integer>() {
    @Override
    public void onComplete(@Nullable Integer result) {

    }
});

TaskRunner.getInstance().execute(new Runnable() {
    @Override
    public void run() {

    }
});

注意:不要忘記關閉執行程序服務

TaskRunner.getInstance().shutdownService();

您可以遷移到下一個方法取決於您的需要

  • 線程+處理程序
  • 執行者
  • 未來
  • 意圖服務
  • 作業調度器
  • RxJava
  • 協程 (Kotlin)

[Android 異步變體]

Google 正在棄用 Android 11 中的 Android AsyncTask API 並建議使用java.util.concurrent 你可以在這里查看提交

 *
 * @deprecated Use the standard <code>java.util.concurrent</code> or
 *   <a href="https://developer.android.com/topic/libraries/architecture/coroutines">
 *   Kotlin concurrency utilities</a> instead.
 */
@Deprecated
public abstract class AsyncTask<Params, Progress, Result> {

如果您在 Android 中維護具有異步任務的舊代碼庫,那么您將來可能必須對其進行更改。 我的問題是,應該使用java.util.concurrent正確替換下面顯示的代碼片段。 Activity的static內部class。 我正在尋找可以與minSdkVersion 16一起使用的東西

private static class LongRunningTask extends AsyncTask<String, Void, MyPojo> {
        private static final String TAG = MyActivity.LongRunningTask.class.getSimpleName();
        private WeakReference<MyActivity> activityReference;

        LongRunningTask(MyActivity context) {
            activityReference = new WeakReference<>(context);
        }

        @Override
        protected MyPojo doInBackground(String... params) {
            // Some long running task
            
        }

        @Override
        protected void onPostExecute(MyPojo data) {

            MyActivity activity = activityReference.get();
            activity.progressBar.setVisibility(View.GONE);
            populateData(activity, data) ;
        }     


    }

HandlerThread可以用作AsyncTask的替代品。 它們是長時間運行的線程。 HandlerThread 的一個例子如下:

您可以創建兩個處理程序對象。 其中之一將用於將消息從 workerThread 發送到 UI 線程。

Handler uiHandler,workerHandler;
Message msg;
HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
Handler.Callback callback=new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            // handle messages sent from working thread (like updating UI)...
            return true;
        }
    }
uiHandler=new Handler(callback);
workerHandler = new Handler(handlerThread.getLooper());
workerHandler.post(new Runnable(){
           // Perform required task
           uiHandler.sendMessage(msg); // this message will be sent to and handled by UI Thread
});

另外,請記住 HandlerThreads 在您的活動生命周期之外運行,因此需要正確清理它們,否則您將出現線程泄漏。 您可以在 Activity 的 onDestroy() 中使用 quit() 或 quitSafely() 方法來防止線程泄漏。

這是我的代碼

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public abstract class AsyncTaskRunner<T> {

    private ExecutorService executorService = null;
    private Set<Callable<T>> tasks = new HashSet<>();

    public AsyncTaskRunner() {
        this.executorService = Executors.newSingleThreadExecutor();
    }
    
    public AsyncTaskRunner(int threadNum) {
        this.executorService = Executors.newFixedThreadPool(threadNum);
    }


    public void addTask(Callable<T> task) {
        tasks.add(task);
    }

    public void execute() {
        try {
            List<Future<T>> features = executorService.invokeAll(tasks);

            List<T> results = new ArrayList<>();
            for (Future<T> feature : features) {
                results.add(feature.get());
            }
            this.onPostExecute(results);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            this.onCancelled();
        } finally {
            executorService.shutdown();
        }

    }

    protected abstract void onPostExecute(List<T> results);

    protected void onCancelled() {
        // stub
    }

}

和用法示例。 擴展AsyncTaskRunner類,

class AsyncCalc extends AsyncTaskRunner<Integer> {

    public void addRequest(final Integer int1, final Integer int2) {
        this.addTask(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                // Do something in background
                return int1 + int2;
            }
        });
    }

    @Override
    protected void onPostExecute(List<Integer> results) {
        for (Integer answer: results) {
            Log.d("AsyncCalc", answer.toString());
        }
    }
}

然后使用它!

AsyncCalc calc = new AsyncCalc();
calc.addRequest(1, 2);
calc.addRequest(2, 3);
calc.addRequest(3, 4);
calc.execute();

文檔說:

AsyncTask此類在 API 級別 30 中已棄用。請改用標准的 java.util.concurrent 或 Kotlin 並發實用程序。

您需要使用Handler或協程而不是AsyncTask

使用 Java 處理程序

new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
    @Override
    public void run() {
        // Your Code
    }
}, 3000);

為 Kotlin 使用處理程序

Handler(Looper.getMainLooper()).postDelayed({
    // Your Code
}, 3000)

我的答案與其他答案相似,但更容易閱讀 imo。

這是類:

public class Async {

    private static final ExecutorService executorService = Executors.newCachedThreadPool();

    private static final Handler handler = new Handler(Looper.getMainLooper());

    public static <T> void execute(Task<T> task) {
        executorService.execute(() -> {
            T t = task.doAsync();
            handler.post(() -> {
                task.doSync(t);
            });
        });
    }

    public interface Task<T> {
        T doAsync();

        void doSync(T t);
    }

}

這是一個關於如何使用它的例子:

    String url;
    TextView responseCodeText;
    
    Async.execute(new Async.Task<Integer>() {
        
        @Override
        public Integer doAsync() {
            try {
                HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
                return connection.getResponseCode();
            } catch (IOException e) {
                return null;
            }
        }

        @Override
        public void doSync(Integer responseCode) {
            responseCodeText.setText("responseCode=" + responseCode);
        }
        
    });

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM