Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android開發6:Service的使用(簡單音樂播放器的實現),androidservice

Android開發6:Service的使用(簡單音樂播放器的實現),androidservice

編輯:關於android開發

Android開發6:Service的使用(簡單音樂播放器的實現),androidservice


前言

  啦啦啦~各位好久不見啦~博主最近比較忙,而且最近一次實驗也是剛剛結束~

  好了不廢話了,直接進入我們這次的內容~

  在這篇博文裡我們將學習Service(服務)的相關知識,學會使用 Service 進行後台工作, 學會使用 Service 與 Activity 進行通信,並在此知識基礎上學會使用 MediaPlayer和簡單的多線程編程、使用 Handle 更新 UI,並設計成功一個簡單的音樂播放器。

  是不是很高大上呢~一起來學習~

 

基礎知識

  Service作為Android四大組件之一,在每一個應用程序中都扮演著非常重要的角色。它主要用於在後台處理一些耗時的邏輯,或者去執行某些需要長期運行的任務。必要的時候我們甚至可以在程序退出的情況下,讓Service在後台繼續保持運行狀態。

  所以Service通常被稱為“後台服務”,其中“後台”一詞是相對於前台而言的,具體是指其本身的運行並不依賴於用戶可視的UI界面,因此,從實際業務需求上來理解,Service的適用場景應該具備以下條件:

    (1)並不依賴於用戶可視的UI界面(當然,這一條其實也不是絕對的,如前台Service就是與Notification界面結合使用的);

    (2)具有較長時間的運行特性。

 

1.Service AndroidManifest.xml 聲明

  一般而言,從Service的啟動方式上,可以將Service分為Started Service和Bound Service。無論哪種具體的Service啟動類型,都是通過繼承Service基類自定義而來。在使用Service時,要想系統能夠找到此自定義Service,無論哪種類型,都需要在AndroidManifest.xml中聲明,語法格式如下:

<service android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:isolatedProcess=["true" | "false"]
    android:label="string resource"
    android:name="string"
    android:permission="string"
    android:process="string" >
    . . .
</service>

  其中,android:name對應Service類名,android:permission是權限聲明,android:process設置具體的進程名稱。需要注意的是Service能否單獨使用一個進程與其啟動方式有關,本後下面會給出具體說明。其他的屬性此處與其他組件基本相同,不再過多描述。

  注:如果自定義Service沒有在AndroidManifest.xml中聲明,當具體使用時,不會像Activity那樣直接崩潰報錯,對於顯式Intent啟動的Service,此時也會給出waring信息“IllegalArgumentException: Service not registered”,有時候不容易發現忘了聲明而一時定位不到問題。

 

2.Started Service

 Started Service相對比較簡單,通過context.startService(Intent serviceIntent)啟動Service,context.stopService(Intent serviceIntent)停止此Service。當然,在Service內部,也可以通過stopSelf(...)方式停止其本身。

 

1)Started Service自定義

下面代碼片段顯示的是一個最基本的Started Service的自定義方式:

public class MyService extends Service {

    public static final String TAG = "MyService";

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.w(TAG, "in onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.w(TAG, "in onStartCommand");
        Log.w(TAG, "MyService:" + this);
        String name = intent.getStringExtra("name");
        Log.w(TAG, "name:" + name);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "in onDestroy");
    }
}

  其中,onBind(...)函數是Service基類中的唯一抽象方法,子類都必須重寫實現,此函數的返回值是針對Bound Service類型的Service才有用的,在Started Service類型中,此函數直接返回 null 即可。onCreate(...)、onStartCommand(...)和onDestroy()都是Started Service相應生命周期階段的回調函數。

 

2) Started Service使用

public class MainActivity extends Activity {

    public static final String TAG = "MainActivity";

    private Button startServiceBtn;
    private Button stopServideBtn;
    private Button goBtn;

    private Intent serviceIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startServiceBtn = (Button) findViewById(R.id.start_service);
        stopServideBtn = (Button) findViewById(R.id.stop_service);
        goBtn = (Button) findViewById(R.id.go);

        startServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                serviceIntent = new Intent(MainActivity.this, MyService.class);
                startService(serviceIntent);
            }
        });

        stopServideBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(serviceIntent);
            }
        });

        goBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, BActivity.class);
                startActivity(intent);
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "in onDestroy");
    }
}

如上代碼片段。

 

  當Client調用startService(Intent serviceIntent)後,如果MyService是第一次啟動,首先會執行 onCreate()回調,然後再執行onStartCommand(Intent intent, int flags, int startId),當Client再次調用startService(Intent serviceIntent),將只執行onStartCommand(Intent intent, int flags, int startId),因為此時Service已經創建了,無需執行onCreate()回調。無論多少次的startService,只需要一次stopService()即可將此Service終止,執行onDestroy()函數(其實很好理解,因為onDestroy()與onCreate()回調是相對的)。

 

  下面重點關注下onStartCommand(Intent intent, int flags, int startId)方法。

  其中參數flags默認情況下是0,對應的常量名為START_STICKY_COMPATIBILITY。startId是一個唯一的整型,用於表示此次Client執行startService(...)的請求請求標識,在多次startService(...)的情況下,呈現0,1,2....遞增。另外,此函數具有一個int型的返回值,具體的可選值及含義如下:

  START_NOT_STICKY:當Service因為內存不足而被系統kill後,接下來未來的某個時間內,即使系統內存足夠可用,系統也不會嘗試重新創建此Service。除非程序中Client明確再次調用startService(...)啟動此Service。

  START_STICKY:當Service因為內存不足而被系統kill後,接下來未來的某個時間內,當系統內存足夠可用的情況下,系統將會嘗試重新創建此Service,一旦創建成功後將回調onStartCommand(...)方法,但其中的Intent將是null,pendingintent除外。

  START_REDELIVER_INTENT:與START_STICKY唯一不同的是,回調onStartCommand(...)方法時,其中的Intent將是非空,將是最後一次調用startService(...)中的intent。

  START_STICKY_COMPATIBILITY:compatibility version of {@link #START_STICKY} that does not guarantee that {@link #onStartCommand} will be called again after being killed。此值一般不會使用,所以注意前面三種情形就好。

 

3) Started Service生命周期及進程相關

  1.onCreate(Client首次startService(..)) >> onStartCommand >> onStartCommand - optional ... >> onDestroy(Client調用stopService(..))

  注:onStartCommand(..)可以多次被調用,onDestroy()與onCreate()想匹配,當用戶強制kill掉進程時,onDestroy()是不會執行的。

  2.對於同一類型的Service,Service實例一次永遠只存在一個,而不管Client是否是相同的組件,也不管Client是否處於相同的進程中。

  3.Service通過startService(..)啟動Service後,此時Service的生命周期與Client本身的什麼周期是沒有任何關系的,只有Client調用stopService(..)或Service本身調用stopSelf(..)才能停止此Service。當然,當用戶強制kill掉Service進程或系統因內存不足也可能kill掉此Service。

  4.Client A 通過startService(..)啟動Service後,可以在其他Client(如Client B、Client C)通過調用stopService(..)結束此Service。

  5.Client調用stopService(..)時,如果當前Service沒有啟動,也不會出現任何報錯或問題,也就是說,stopService(..)無需做當前Service是否有效的判斷。

  6.startService(Intent serviceIntent),其中的intent既可以是顯式Intent,也可以是隱式Intent,當Client與Service同處於一個App時,一般推薦使用顯示Intent。當處於不同App時,只能使用隱式Intent。

 

當Service需要運行在單獨的進程中,AndroidManifest.xml聲明時需要通過android:process指明此進程名稱,當此Service需要對其他App開放時,android:exported屬性值需要設置為true(當然,在有intent-filter時默認值就是true)。

<service
    android:name=".MyService"
    android:exported="true"
    android:process=":MyCorn" >
    <intent-filter>
        <action android:name="com.example.androidtest.myservice" />
    </intent-filter>
</service>

 

4)Started Service Client與Service通信相關

  當Client調用startService(Intent serviceIntent)啟動Service時,Client可以將參數通過Intent直接傳遞給Service。Service執行過程中,如果需要將參數傳遞給Client,一般可以通過借助於發送廣播的方式(此時,Client需要注冊此廣播)。

 

3.Bound Service

  相對於Started Service,Bound Service具有更多的知識點。Bound Service的主要特性在於Service的生命周期是依附於Client的生命周期的,當Client不存在時,Bound Service將執行onDestroy,同時通過Service中的Binder對象可以較為方便進行Client-Service通信。Bound Service一般使用過程如下:

  1.自定義Service繼承基類Service,並重寫onBind(Intent intent)方法,此方法中需要返回具體的Binder對象;

  2.Client通過實現ServiceConnection接口來自定義ServiceConnection,並通過bindService (Intent service, ServiceConnection sc, int flags)方法將Service綁定到此Client上;

  3.自定義的ServiceConnection中實現onServiceConnected(ComponentName name, IBinder binder)方法,獲取Service端Binder實例;

  4.通過獲取的Binder實例進行Service端其他公共方法的調用,以完成Client-Service通信;

  5.當Client在恰當的生命周期(如onDestroy等)時,此時需要解綁之前已經綁定的Service,通過調用函數unbindService(ServiceConnection sc)。

   在Bound Service具體使用過程中,根據onBind(Intent intent)方法放回的Binder對象的定義方式不同,又可以將其分為以下三種方式,且每種方式具有不同的特點和適用場景:

 

  1).Extending the Binder class

   這是Bound Service中最常見的一種使用方式,也是Bound Service中最簡單的一種。

  局限:Clinet與Service必須同屬於同一個進程,不能實現進程間通信(IPC)。否則則會出現類似於“android.os.BinderProxy cannot be cast to xxx”錯誤。

 

下面通過代碼片段看下具體的使用:

public class MyBindService extends Service {

    public static final String TAG = "MyBindService";

    private MyBinder mBinder = new MyBinder();

    public class MyBinder extends Binder {
        MyBindService getService() {
            return MyBindService.this;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.w(TAG, "in onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.w(TAG, "in onBind");
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.w(TAG, "in onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "in onDestroy");
    }
}
public class BActivity extends Activity {

    public static final String TAG = "BActivity";

    private Button bindServiceBtn;
    private Button unbindServiceBtn;

    private Button startIntentService;

    private Intent serviceIntent;

    private ServiceConnection sc = new MyServiceConnection();
    private MyBinder mBinder;
    private MyBindService mBindService;
    private boolean mBound;

    private class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Log.w(TAG, "in MyServiceConnection onServiceConnected");
            mBinder = (MyBinder) binder;
            mBindService = mBinder.getService();

            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            Log.w(TAG, "in MyServiceConnection onServiceDisconnected");
            mBound = false;
        }

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.b);

        bindServiceBtn = (Button) findViewById(R.id.bind_service);
        unbindServiceBtn = (Button) findViewById(R.id.unbind_service);
        startIntentService = (Button) findViewById(R.id.start_intentservice);

        bindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(BActivity.this, MyBindService.class);
                bindService(intent, sc, Context.BIND_AUTO_CREATE);
            }
        });

        unbindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                excuteUnbindService();
            }
        });

        startIntentService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(BActivity.this, MyIntentService.class);
                startService(intent);
            }
        });

    }

    private void excuteUnbindService() {
        if (mBound) {
            unbindService(sc);
            mBound = false;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "in onDestroy");
        excuteUnbindService();
    }
}

首次點擊bindServiceBtn進行bindService(..)時,依次回調順序如下:

  1 MyBindService(13457): in onCreate

  2 MyBindService(13457): in onBind

  3 BActivity(13457): in MyServiceConnection onServiceConnected

再次點擊bindServiceBtn按鈕時,發現沒有任何輸出,說明MyBindService沒有進行任何回調。

點擊unbindServiceBtn進行unbindService(..)時,回調順序為:

1 MyBindService(13457): in onUnbind

2 MyBindService(13457): in onDestroy

 注:在四大基本組件中,需要注意的的是BroadcastReceiver不能作為Bound Service的Client,因為BroadcastReceiver的生命周期很短,當執行完onReceive(..)回調時,BroadcastReceiver生命周期完結。而Bound Service又與Client本身的生命周期相關,因此,Android中不允許BroadcastReceiver去bindService(..),當有此類需求時,可以考慮通過startService(..)替代。

 

4.Service特性

  1.Service本身都是運行在其所在進程的主線程(如果Service與Clinet同屬於一個進程,則是運行於UI線程),但Service一般都是需要進行”長期“操作,所以經常寫法是在自定義Service中處理”長期“操作時需要新建線程,以免阻塞UI線程或導致ANR;

  2.Service一旦創建,需要停止時都需要顯示調用相應的方法(Started Service需要調用stopService(..)或Service本身調用stopSelf(..), Bound Service需要調用unbindService(..)),否則對於Started Service將處於一直運行狀態,對於Bound Service,當Client生命周期結束時也將因此問題。也就是說,Service執行完畢後,必須人為的去停止它。

 

5.IntentService

IntentService是系統提供給我們的一個已經繼承自Service類的特殊類,IntentService特殊性是相對於Service本身的特性而言的:

1.默認直接實現了onBind(..)方法,直接返回null,並定義了抽象方法onHandlerIntent(..),用戶自定義子類時,需要實現此方法;

2.onHandlerIntent(..)主要就是用來處於相應的”長期“任務的,並且已經自動在新的線程中,用戶無語自定義新線程;

3.當”長期“任務執行完畢後(也就是onHandlerIntent(..)執行完畢後),此IntentService將自動結束,無需人為調用方法使其結束;

4.IntentService處於任務時,也是按照隊列的方式一個個去處理,而非真正意義上的多線程並發方式。

 

下面是一個基本的繼承自IntentService的自定義Service:

public class MyIntentService extends IntentService {

    public static final String TAG = "MyIntentService";

    public MyIntentService() {
        super(TAG);
    }

    public MyIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.w(TAG, "in onHandleIntent");
        Log.w(TAG, "thread name:" + Thread.currentThread().getName());
    }

}

 

6.Service和Activity通信

  上面我們學習了Service的基本用法,啟動Service之後,就可以在onCreate()或onStartCommand()方法裡去執行一些具體的邏輯了。不過這樣的話Service和Activity的關系並不大,只是Activity通知了Service一下:“你可以啟動了。”然後Service就去忙自己的事情了。那麼有沒有什麼辦法能讓它們倆的關聯更多一些呢?比如說在Activity中可以指定讓Service去執行什麼任務。當然可以,只需要讓Activity和Service建立關聯就好了。

觀察MyService中的代碼,你會發現一直有一個onBind()方法我們都沒有使用到,這個方法其實就是用於和Activity建立關聯的,修改MyService中的代碼,如下所示:

public class MyService extends Service {  
  
    public static final String TAG = "MyService";  
  
    private MyBinder mBinder = new MyBinder();  
  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        Log.d(TAG, "onCreate() executed");  
    }  
  
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        Log.d(TAG, "onStartCommand() executed");  
        return super.onStartCommand(intent, flags, startId);  
    }  
  
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        Log.d(TAG, "onDestroy() executed");  
    }  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        return mBinder;  
    }  
  
    class MyBinder extends Binder {  
  
        public void startDownload() {  
            Log.d("TAG", "startDownload() executed");  
            // 執行具體的下載任務  
        }  
  
    }  
  
}  

 

實驗內容

實現一個簡單的播放器,要求功能有:

1. 播放、暫停,停止,退出功能;

2. 後台播放功能;

3. 進度條顯示播放進度、拖動進度條改變進度功能;

4. 播放時圖片旋轉,顯示當前播放時間功能。

               

       

打開程序主頁面                       開始播放                                   暫停                                          停止

 

實驗內容相關知識 

 1. MediaPlayer 介紹

常用方法

 

2. 向虛擬機添加文件

首先打開 Android Device Monitor,如下圖:

使用自己手機進行調試時,注意下把文件拷到內置 SD 卡而不是外置 SD 卡會比較方 便。要使用外置的 SD 卡時,注意下文件路徑的獲取。這是相關的路徑獲取方 法:http://blog.sina.com.cn/s/blog_5da93c8f0102vcam.html

 

3.使用 MediaPlayer

創建對象初始化:

注意下獲取的文件路徑,若是使用模擬器的如下,若是使用自己手機的內置 SD 卡則使 用:Environment.getExternalStorageDirectory()  +   "/data/music.mp3

 

播放/暫停:

停止:

 

4.Service 的使用

創建 service 類,實現 MediaPlayer 的功能。 注意在 AndroidManifest.xml 文件裡注冊 Service:

通過 Binder 來保持 Activity 和 Service 的通信(寫在 service 類):

在 Activity 中調用 bindService 保持與 Service 的通信(寫在 activity 類): Activity 啟動時綁定 Service:

bindService 成功後回調 onServiceConnected 函數,通過 IBinder 獲取 Service 對 象,實現 Activity 與 Service 的綁定:

停止服務時,必須解除綁定,寫入退出按鈕中:

此時,在 Activity 的 onCreate 方法中執行上述與 Service 通信的方法後,即可實現 後台播放。點擊退出按鈕,程序會退出,音樂停止;返回桌面,音樂繼續播放。

 

5.Handler的使用

Handler 與 UI 是同一線程,這裡可以通過 Handler 更新 UI 上的組件狀態,Handler 有很多方法,這裡使用比較簡便的 post 和 postDelayed 方法。

 

使用 Seekbar 顯示播放進度,設置當前值與最大值:

定義 Handler:run 函數中進行更新 seekbar 的進度在類中定義簡單日期格式,用來顯 示播放的時間,用 time.format 來格式所需要的數據,用來監聽進度條的滑動變化:

 

實驗過程

本次實驗主要是實現一個音樂播放器,首先打開Android Device Monitor, 向虛擬機添加音樂文件。

創建 service 類,使用 MediaPlayer,創建對象,設置一個布爾代數變量作為我們判斷音樂此時狀態的tag,使用setDataSource方法調用虛擬設備中的音樂文件,並對其進行初始化:

 

 

在 AndroidManifest.xml 文件裡注冊 Service:

 

 

並在其中聲明SD卡的讀寫權限:

 

 

通過 Binder 來保持 Activity 和 Service 的通信(寫在 service 類):

 

在Activity中調用 bindService 保持與 Service 的通信:

 

Activity 啟動時綁定 Service。

 

bindService成功後回調onServiceConnected 函數,通過IBinder 獲取 Service對象,實現Activity與 Service的綁定:

 

在Activity中設置按鈕點擊事件時我們需要調用MediaPlayer中的某些方法,這裡我們先在service類中實現MediaPlayer的功能:

 

此時,在 Activity 的 onCreate方法中執行上述與 Service 通信的方法後,即可實現後台播放。點擊退出按鈕,程序會退出,音樂停止;返回桌面,音樂繼續播放。

 

  Handler 與 UI 是同一線程,這裡可以通過 Handler 更新 UI 上的組件狀態,通過Handler可以統一進行對UI的管理,因為Handler采用消息處理的機制。簡單理解就是另一個線程發送一個編號給消息所在的線程,那麼該線程的消息處理程序就會收到該消息並進行處理,而消息采用int類型,所以int能夠表示多少種數字就以為著有多少消息可以給你使用(因為存在系統的消息,所以可能有一部分被系統的消息占有,而不能使用)。Handler有很多方法,這裡使用比較簡便的 post和postDelayed 方法。

定義 Handler:

 

 

run 函數中進行更新 seekbar 的進度,在類中定義簡單日期格式,用來顯示播放的時間,用 time.format 來格式所需要的數據,用來監聽進度條的滑動變化(使用 Seekbar 顯示播放進度,設置當前值與最大值,具體事件在onCreate方法中實現):

 

 

  接下來,我們需要完成各個按鈕的點擊事件。在點擊事件中,為了完成圖片旋轉以及其實時更新,我們使用animator屬性.我們首先實例化了一個ObjectAnimator,然後設置所需的參數:imageView:需要更改的View,動畫類型,動畫范圍,並在對應的點擊事件中引用animator中的一些事件,實現動畫的點擊動作:

 

 

在點擊事件中,主要是實現按鈕文本的變換以及調用service中定義好的MediaPlayer事件,並實現動畫的變換:

 

 

這裡需要注意的是,在btnPlayOrPause.setOnClickListener中,為了正確實現動畫事件,我們需要設置相應的tag,由tag的變換來控制事件的調用:

 

 

而且停止服務時,必須解除綁定,寫入btnQuit按鈕中:

 

最後,在onCreate函數中,我們將定義的各個函數一一實現,即可完成實驗的功能。

 

除此之外,為了保證返回後台(按了返回鍵後)仍能繼續播放,並且打開應用後Activity中的內容要與音樂當前的狀態對應,我們還需要獲取並設置返回鍵的點擊事件:

 

完成實驗~

 

注意事項

在設置mediaPlayer時出現了android mediaPlayer error (-38,0)的報錯。

 

在查詢了一些資料後,解決了這個問題:

android mediaPlayer error (-38,0)出現這個錯誤發現在mediaPlayer.reset()後調用了mediaPlayer.getDuration()在沒有給mediaPlayer對象設置數據源之前,是不能使用getDuration等這些方法的

 

這時,需要檢查一下在設置MediaPlayer的數據源時,使用的是那種方式:

1.在初始化MediaPlayer時,通過create方法設置數據源。則不能寫MediaPlayer.prepare()方法,這時,會報錯。

2.如果是使用MediaPlayer構造函數初始化MediaPlayer,然後通過setDataSource方法設置數據源時,就需要在start()之前,使用MediaPlayer.prepare()方法,對數據源進行一次編譯。能夠避免出現(-38,0)這種錯誤。

 

實驗截圖

 

 

 

 

源碼下載

源碼下載點擊這裡~

 

1、本實驗實驗環境:

操作系統 Windows 10 

實驗軟件 Android Studio 2.2.1

虛擬設備:Nexus_5X

API:23

2、貼代碼的時候由於插入代碼框的大小問題,代碼格式不太嚴整,望見諒~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved