国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開(kāi)通VIP
OPhone網(wǎng)絡(luò)應(yīng)用編程實(shí)例: 豆瓣電臺(tái)客戶端 - 技術(shù)文章 - OPhone SDN [...

說(shuō)明:本文的客戶端是開(kāi)發(fā)版本,電臺(tái)logo等圖標(biāo)做了虛化處理,以避免誤導(dǎo); 另外源碼涉及到豆瓣內(nèi)部api的地方也省略。

功能需求

下面是豆瓣電臺(tái)網(wǎng)絡(luò)版和客戶端(開(kāi)發(fā)版)的界面。


豆瓣電臺(tái)網(wǎng)絡(luò)版

下圖的豆瓣電臺(tái)客戶端和網(wǎng)絡(luò)版一樣,除了播放歌曲,顯示專輯 封面, 播放時(shí)間,歌手和歌的名稱,以及喜歡/不喜歡,垃圾桶,跳過(guò)的3個(gè)操作按鈕外,還涉及到登錄,更換用戶,暫停的操作。

設(shè)計(jì)需求

  1. 豆瓣電臺(tái)是網(wǎng)絡(luò)音樂(lè)服務(wù),客戶端需要有后臺(tái)播放的服務(wù),前臺(tái)的播放器以及任務(wù)條的通知;
  2. 推薦給用戶的歌曲列表是從豆瓣網(wǎng)站上獲取的,歌曲也是在線播放的,需要網(wǎng)絡(luò)數(shù)據(jù)的獲取和異常處理;
  3. 本地還要保存一些數(shù)據(jù),比如播放歷史和自動(dòng)登錄使用的信息;
  4. 網(wǎng)絡(luò)操作都是比較耗時(shí)的,所以操作需要做成異步的方式來(lái)通知更新UI;
  5. 針對(duì)手機(jī)應(yīng)用的功能:比如接電話自動(dòng)暫停,掛電話自動(dòng)繼續(xù)播放,橫豎屏轉(zhuǎn)換要加載不同的UI Layout;
  6. 利用OPhone平臺(tái)提供的一些特性來(lái)改進(jìn)用戶體驗(yàn),比如使用Toast提示,動(dòng)畫效果,重力感應(yīng),手勢(shì)等

架構(gòu)設(shè)計(jì)

針對(duì)上面的應(yīng)用本身的功能和設(shè)計(jì)的需求,主要的架構(gòu)就是前臺(tái)Player的Activity,主要負(fù)責(zé)界面顯示和用戶的交互; 加上后臺(tái)的 Service,負(fù)責(zé)歌曲的播放和向豆瓣服務(wù)器發(fā)送請(qǐng)求,是個(gè)C/S的結(jié)構(gòu)。

如下圖所示:

Player和Service的架構(gòu)

Player調(diào)用Service

使用OMS平臺(tái)提供的service機(jī)制,通過(guò)下面的aidl把接口定義好。比如用戶點(diǎn)跳過(guò)按鈕時(shí),Player就會(huì)調(diào)用Service的skip。

  1. interface IRadioService{   
  2.     void stop();   
  3.     void exit();   
  4.     void play();   
  5.     void like(boolean isLike);   
  6.     void skip();   
  7.     void hate();   
  8.     boolean isPlaying();   
  9.     Song getSongPlaying();   
  10.     void logout();   
  11.     Bitmap getSongPicture();   
  12.     int pos();   
  13.     int duration();   
  14. }  

這里值得注意的是service的啟動(dòng)方式:

  1. private static final Intent service_intent = new Intent(Consts.INTENT_RADIO_SERVICE);   
  2. protected void onCreate(Bundle savedInstanceState){   
  3.         super.onCreate(savedInstanceState);   
  4.         startService(service_intent);   
  5.     }  

在Player的onCreate方法里面使用startService,這樣在Player Activity退出時(shí),Service進(jìn)程不會(huì)被殺死。而使用bindService方式 啟動(dòng)的service會(huì)在所有的client unbind后結(jié)束。

然后要在onResume和onDestroy的時(shí)候分別進(jìn)行bindService和unbindService。

  1. protected void onResume(){   
  2.     super.onResume();   
  3.     bindService(service_intent, connection, Context.BIND_AUTO_CREATE);   
  4. }   
  5. protected void onDestroy(){   
  6.     super.onDestroy();   
  7.     unbindService(connection);   
  8. }  

另外,如果希望service返回你自定義的對(duì)象,你需要實(shí)現(xiàn)parcelable接口,比如上面的Song。

Service里面的Handler

因?yàn)榫W(wǎng)絡(luò)通訊都是比較耗時(shí)的。比如上面的skip,是要向豆瓣提交跳過(guò)的是哪首歌的信息,并獲取新的播放列表。實(shí)際上,為了比較好的用戶體驗(yàn),用戶點(diǎn)跳過(guò)按鈕時(shí),在該按鈕的onClick方法里面調(diào)用了Service的skip,而Service只是向Downloader發(fā)了一個(gè)消息,告訴執(zhí)行了skip操作就返回了。這樣按鈕就不會(huì)一直停在按下去的狀態(tài)。

  1. private Handler downloader = new Handler() {   
  2.         public void handleMessage(Message msg) {   
  3.             Bundle bundle = msg.getData();   
  4.             switch (msg.what){   
  5.                 case Consts.MSG_PLAYLIST_REQUIRE:   
  6.                     //下載播放列表   
  7.                     requireList(...);   
  8.                     //播放下一首   
  9.                     playNext();   
  10.                     break;   
  11.                 case Consts.MSG_PICTURE_DOWNLOADING:   
  12.                     //下載圖片   
  13.                     pic = web.getImage(bundle.getString("pic_url"));   
  14.                     //圖片下載完成,通知Player更新圖片   
  15.                     Intent intent = new Intent(Consts.INTENT_UPDATE_SONG_PICTURE);   
  16.                     sendBroadcast(intent);   
  17.                     break;   
  18.                 ...   
  19.             }   
  20.         }   
  21.     };  

如上面代碼所示,在service里面的Downloader是一個(gè)Handler, Handler本身實(shí)現(xiàn)了一個(gè)消息隊(duì)列,在handleMessage函數(shù)里面來(lái)處理消息。這里的Handler是在service線程的,并沒(méi)有新起線程。因?yàn)檫@里用Handler最主要的目的是使Player的調(diào)用馬上返回,達(dá) 到異步的目的。上面的skip就是向Downloader發(fā)了一個(gè)消息,而Downloader收到這個(gè)消息后,就去下載新的列表。

這里的Downloader最早的設(shè)計(jì)是在一個(gè)Thread里的,但是那樣需要service的主線程也要有一個(gè)handler來(lái)處理Thread發(fā)給主線程 的消息,比較復(fù)雜。而且對(duì)于電臺(tái)本身來(lái)講,都是先下載播放列表,開(kāi)始播放后,才需要下載正在播的歌曲的封面圖片,所以不需要真正的并發(fā)下載,也就是說(shuō),同一時(shí)刻只有一個(gè)下載的任務(wù)在執(zhí)行就可以了。所以最后是使用現(xiàn)在的設(shè)計(jì),可以避免多創(chuàng)建一個(gè)線 程,成本更低。

Service通知Player

上面講了Player怎么調(diào)用Service的,但Service還需要通知Player。比如當(dāng)圖片下載完成時(shí), Service需要通知Player來(lái)拿圖片,因?yàn)?Service和Player是不同進(jìn)程,所以在Player里面注冊(cè)了一個(gè)Receiver來(lái)實(shí)現(xiàn)的。

BroadcastReceiver本身可以接受Intent,也可以設(shè)置filter接受特定的Intent,然后在onReceive函數(shù)里面來(lái)具體處理。

  1.     
  2. private BroadcastReceiver receiver = new BroadcastReceiver(){   
  3.     public void onReceive(Context context, Intent intent) {   
  4.         if (intent.getAction.equals(Consts.INTENT_UPDATE_SONG_PICTURE)){   
  5.             updateSongPicture(); //更新專輯封面   
  6.         }   
  7.         ...   
  8.     }   
  9. }  

上面的代碼里,就是在Service里面圖片下載完成后sendBroadcast,然后Receiver收到后去更新專輯封面。這里要注意的是,要在 Player的onResume和onPause方法里面分別調(diào)用registerReceiver和unregisterReceiver。

  1. protected void onResume(){   
  2.     super.onResume();   
  3.     ...   
  4.     filter = new IntentFilter();   
  5.     filter.addAction(Consts.INTENT_UPDATE_SONG_PICTURE);   
  6.     ...   
  7.     registerReceiver(receiver, filter);   
  8. }   
  9. protected void onPause(){   
  10.     super.onPause();   
  11.     unregisterReceiver(receiver);   
  12. }  

Login里面的Handler

Login也是一個(gè)單獨(dú)的Activity,左圖為L(zhǎng)ogin的頁(yè)面,因?yàn)榈卿洷?較耗時(shí),所以要顯示一個(gè)有進(jìn)度的提示框給用戶,告訴用戶正在 登錄。

這個(gè)時(shí)候,登錄的網(wǎng)絡(luò)操作是新起一個(gè)線程去做的,因?yàn)镺Phone的 UI是單線程的模型,在子線程里是不能直接去碰UI的。所以子線 程完成登錄要在主線程里面有一個(gè)Handler去處理子線程的消息 來(lái)更新UI。

如下圖所示

下面是在登錄按鈕的OnClickListener里面onClick方法里面的調(diào)用,向子線程發(fā)完消息后就會(huì)返回,不會(huì)阻塞住UI。

  1.     
  2. Message msg = looper.handler.obtainMessage(MSG_LOGIN); Bundle bundle = new Bundle();   
  3. ...   
  4. //向子線程發(fā)消息執(zhí)行l(wèi)ogin   
  5. looper.handler.sendMessage(msg);   
  6. //顯示進(jìn)度對(duì)話框   
  7. dialog.show();  

下面的代碼就是主線程里的Handler,里面根據(jù)子線程的消息來(lái)更新UI。

  1. private Handler mainHandler = new Handler() {   
  2.     public void handleMessage(Message msg) {   
  3.         switch (msg.what){   
  4.             case MSG_DONE:   
  5.                 dialog.dismiss(); //把對(duì)話框關(guān)掉   
  6.                 int error = msg.arg1;   
  7.                 if (error == 0){   
  8.                     Intent intent = new Intent(Consts.INTENT_RADIO_PLAYER);   
  9.                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);   
  10.                     startActivity(intent); //啟動(dòng)Player   
  11.                     finish();   
  12.                 }else{   
  13.                     showToast(error); //顯示錯(cuò)誤提示   
  14.                 }   
  15.             ...   
  16.         }   
  17.     }   
  18. };  

下面是執(zhí)行登錄的子線程,這里使用了平臺(tái)提供的Looper,可以方便的在線程里實(shí)現(xiàn)一個(gè)消息隊(duì)列,然后用一個(gè)Handler來(lái)處理消 息。Looper的用法很簡(jiǎn)單,只需要在循環(huán)的開(kāi)始和結(jié)束分別進(jìn)行prepare和loop就好了。

  1. private class LooperThread extends Thread {   
  2.     private Handler handler;   
  3.     public void run() {   
  4.         Looper.prepare();   
  5.         handler = new Handler(){   
  6.             public void handleMessage(Message msg) {   
  7.                 switch (msg.what) {   
  8.                     case MSG_LOGIN:   
  9.                         ... //執(zhí)行l(wèi)ogin網(wǎng)絡(luò)操作   
  10.                         Message m = mainHandler.obtainMessage(MSG_DONE);   
  11.                         ...   
  12.                         mainHandler.sendMessage(m); //完成后給主線程發(fā)消息   
  13.                         break;   
  14.                     ...   
  15.                 }   
  16.             };   
  17.         Looper.loop();   
  18.         }   
  19.     }   
  20. }  

OPhone平臺(tái)的進(jìn)程/線程間通訊

上面用到了Service,Receiver和Handler,里面涉及到了不同進(jìn)程,不同線程間的通訊。 進(jìn)程間通訊是基于libutils的Binder機(jī)制的,這里簡(jiǎn)單的總結(jié)一下:

  1. Service和Client是比較緊的耦合,通過(guò)aidl來(lái)定義接口,Service接口返回的數(shù)據(jù)類型必須實(shí)現(xiàn)parcelable。
  2. BroadcastReceiver是廣播和訂閱的模式,比較松散,是用發(fā)送Intent來(lái)通訊的,數(shù)據(jù)的話可以放到Bundle里。

受限于UI的單線程模型,需要把比較耗時(shí)的操作用子線程來(lái)做,避免UI阻塞。平臺(tái)提供了Handler和Looper,可以比較方便的在主線程和子線程之間通訊。這種方法比AsycTask的方式要更靈活,而且避免了反復(fù)創(chuàng)建啟動(dòng)線程的開(kāi)銷。

網(wǎng)絡(luò)操作

下面是網(wǎng)絡(luò)操作涉及到的一些問(wèn)題,使用的是Apache接口。

 設(shè)置連接參數(shù)

  1. HttpParams params = new BasicHttpParams();   
  2. //設(shè)置超時(shí)為Consts.TIMEOUT毫秒   
  3. HttpConnectionParams.setConnectionTimeout(params, Consts.TIMEOUT);   
  4. //buffer大小   
  5. HttpConnectionParams.setSocketBufferSize(params, Consts.BUFFER_SIZE);   
  6. //user agent   
  7. HttpProtocolParams.setUserAgent(params, Consts.USER_AGENT);   
  8. DefaultHttpClient client = new DefaultHttpClient(params);  

HttpGet

以get方法訪問(wèn)一個(gè)url,注意args需要用URLEncoder.encode()處理下。一般網(wǎng)絡(luò)調(diào)用返回JSON格式,可以把返回的字符串轉(zhuǎn)換成 JSONObject就可以處理了。

  1. public String getString(String path, String args) throws Exception{   
  2.     client.getCookieStore().clear(); //清cookie,根據(jù)需要設(shè)置   
  3.     HttpGet get = new HttpGet(new URI("http"null, Consts.HOST, 80, path, args, null));   
  4.     HttpResponse response = client.execute(get);   
  5.     HttpEntity entity = response.getEntity();   
  6.     return EntityUtils.toString(entity);}  

HttpPost

  1. public String post(String url, List <NameValuePair> nvps) throws Exception{   
  2.     HttpPost httpost = new HttpPost(url);   
  3.     httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));   
  4.     HttpResponse response = client.execute(httpost);   
  5.     HttpEntity entity = response.getEntity();   
  6.     return EntityUtils.toString(entity);   
  7. }  

下載圖片

OPhone平臺(tái)沒(méi)有UI控件可以直接顯示一個(gè)網(wǎng)絡(luò)上的圖片,必須要先下載下來(lái)再顯示.

  1. public Bitmap getImage(String url) throws Exception{   
  2.     HttpGet get = new HttpGet(url);   
  3.     HttpResponse response = client.execute(get);   
  4.     HttpEntity entity = response.getEntity();   
  5.     byte[] data = EntityUtils.toByteArray(entity);   
  6.     return BitmapFactory.decodeByteArray(data, 0, data.length);   
  7. }  

數(shù)據(jù)庫(kù)操作

OPhone平臺(tái)的數(shù)據(jù)庫(kù)是Sqlite,并且提供了SQLiteOpenHelper這個(gè)類來(lái)方便使用

數(shù)據(jù)庫(kù)表創(chuàng)建和設(shè)計(jì)

  1. class DatabaseHelper extends SQLiteOpenHelper{   
  2.     public DatabaseHelper(Context context){   
  3.         super(context, DATABASE_NAME, null, DATABASE_VERSION);   
  4.     }   
  5.     public void onCreate(SQLiteDatabase db) {   
  6.         db.execSQL(create_table_sql); //這里創(chuàng)建表   
  7.     }   
  8.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {   
  9.         //當(dāng)數(shù)據(jù)庫(kù)版本升級(jí)的時(shí)候會(huì)調(diào)這個(gè)函數(shù)   
  10.     }   
  11. }  

查詢

這里使用的rawQuery,直接是執(zhí)行sql,我覺(jué)得比別的接口更靈活。這里注意的是,從cursor里取數(shù)據(jù)時(shí),首先要cursor.moveToFirst... 還有記得cursor和help最后都要調(diào)他們的close方法來(lái)釋放資源,否則會(huì)內(nèi)存泄漏。

  1. public String[] getStrings(){   
  2.     SQLiteDatabase db = helper.getReadableDatabase();   
  3.     try {   
  4.         Cursor cursor = db.rawQuery("select col from table"null);   
  5.         int total = cursor.getCount();   
  6.         if (total > 0){   
  7.             String[] ss = new String[total];   
  8.             cursor.moveToFirst();   
  9.             for (int i = 0; i < total; i++){   
  10.                 ss[i] = cursor.getString(0);   
  11.                 cursor.moveToNext();   
  12.             }   
  13.             cursor.close();   
  14.             return ss;   
  15.         }   
  16.         cursor.close();   
  17.     } catch (SQLiteException e) {   
  18.     }   
  19.     return null;   
  20. }  

UI相關(guān)

下面是電臺(tái)這個(gè)應(yīng)用涉及到的一些比較常用的UI組件的用法介紹。

Notification

電臺(tái)在后臺(tái)播放時(shí),Service會(huì)在任務(wù)欄上放一個(gè)通知,以便Player退出時(shí),來(lái)顯示當(dāng)前播放的歌曲,而且點(diǎn)通知會(huì)打開(kāi)Player。Notification在下來(lái)列表里的UI是RemoteView, 可以設(shè)置簡(jiǎn)單的view進(jìn)去,比如TextView,ImageView,也可以用自己的layout,但是不能設(shè)置別的復(fù)雜View。如果有人找到方法,請(qǐng)發(fā)個(gè)mail給我。: )

  1. private void sendNotifcation(){   
  2.     int icon = R.drawable.title_icon;   
  3.     CharSequence tickerText = context.getString(R.string.notification_title);   
  4.     long when = System.currentTimeMillis();   
  5.     if (notification == null){   
  6.         notification = new Notification(icon, tickerText, when);   
  7.         notification.flags = Notification.FLAG_NO_CLEAR; //設(shè)置不能被清除   
  8.     }   
  9.     
  10.     Intent notificationIntent = new Intent(Consts.INTENT_RADIO_PLAYER);   
  11.     PendingIntent contentIntent = PendingIntent.getActivity(this0, notificationIntent, 0);   
  12.     RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.notification);   
  13.     contentView.setImageViewResource(R.id.notification_image, R.drawable.icon);   
  14.     contentView.setTextViewText(R.id.songName, song.title);   
  15.     contentView.setTextViewText(R.id.artistName, song.artist);   
  16.     
  17.     notification.contentView = contentView;   
  18.     notification.contentIntent = contentIntent;   
  19.     
  20.     notifyManager.notify(Consts.NOTIFICATION_PLAY, notification);   
  21. }  

帶進(jìn)度條的對(duì)話框

如果只是像登錄時(shí)那個(gè)登錄進(jìn)度框的話,比較簡(jiǎn)單,就像下面的方式定義一個(gè)ProgressDialog就好了。如果需要自己來(lái)更新進(jìn)度的話,就要在handler里面去更新進(jìn)度值了。

  1. dialog = new ProgressDialog(this);   
  2. dialog.setMessage(getResources().getString(R.string.logging));   
  3. dialog.setIndeterminate(true);   
  4. dialog.setCancelable(true);  

對(duì)話框風(fēng)格的Activity

有時(shí)候你需要一個(gè)長(zhǎng)的像Dialog的Activity,比如之前的登錄頁(yè)面。那么需要就使用style,就像css一樣,但是還沒(méi)那么好用就是了。如下

  1. <activity android:name=".Login" ...    
  2.             android:theme="@style/DoubanTheme.Dialog">   
  3. ...   
  4. </activity>    
  5.     
  6. 而style在res/value/style.xml里面定義   
  7. <resources>   
  8.     <style name="DoubanTheme.Dialog" parent="@android:style/Theme.Dialog">   
  9.         <item name="android:windowNoTitle">true</item>   
  10.         ...   
  11.     </style>   
  12.     ...   
  13. </resources>  

自動(dòng)補(bǔ)齊的輸入框

自動(dòng)補(bǔ)齊的輸入框,也就是AutoCompleteTextView,使用很簡(jiǎn)單,只需要把匹配的數(shù)據(jù)做成一個(gè)ArrayAdapter,然后setAdapter就好了,根據(jù)需要選擇layout,比如simple_dropdown_item_1line。

  1. emailText = (AutoCompleteTextView)findViewById(R.id.emailText);   
  2. emailText.setAdapter(new ArrayAdapter<String>   
  3.     (this, android.R.layout.simple_dropdown_item_1line, emails));  

超鏈接

如果你需要超鏈接,不是那么容易的。比如Login頁(yè)面那個(gè)注冊(cè)的鏈接,是先要寫一個(gè)正則表達(dá)式,然后用Linkify.addLinks把一個(gè)view里的符合正則表達(dá)式的文字加上鏈接。

  1. registerLink = (TextView)findViewById(R.id.registerLink);   
  2. Pattern p = Pattern.compile(getResources().getString(R.string.register_link_text));   
  3. Linkify.addLinks(registerLink, p, Consts.REGISTER_URL);  

菜單

OPhone平臺(tái)創(chuàng)建菜單也比較方便,下面是Player里面Options Menu的用法

  1. public boolean onCreateOptionsMenu(Menu menu){   
  2.     super.onCreateOptionsMenu(menu);   
  3.     menu.add(0, MENU_PAUSE, 0, R.string.menu_pause);   
  4.     menu.add(0, MENU_LOGOUT, 0, R.string.menu_logout);   
  5.     menu.add(0, MENU_QUIT, 0, R.string.menu_quit);   
  6.     return true;   
  7. }  

你也可以根據(jù)需要在prepare的時(shí)候來(lái)更改菜單

  1. public boolean onPrepareOptionsMenu(Menu menu){   
  2.     super.onPrepareOptionsMenu(menu);   
  3.     MenuItem item = menu.findItem(MENU_PAUSE);   
  4.     
  5.     //根據(jù)現(xiàn)在是不是在播放來(lái)顯示是“暫?!边€是"開(kāi)始"的title   
  6.     item.setTitle(isPlaying ? R.string.menu_pause : R.string.menu_start);   
  7.     return true;   
  8. }  

然后在用戶點(diǎn)擊菜單項(xiàng)的時(shí)候處理

  1. public boolean onOptionsItemSelected(MenuItem item) {   
  2.     switch (item.getItemId()) {   
  3.         case MENU_PAUSE:   
  4.             pause();   
  5.             return true;   
  6.         case MENU_LOGOUT:   
  7.             logout();   
  8.             return true;   
  9.         case MENU_QUIT:   
  10.             quit();   
  11.             return true;   
  12.     }   
  13.     return false;   
  14. }  

動(dòng)畫

在歌曲專輯封面圖片的加載時(shí)使用了一個(gè)淡入/淡出的動(dòng)畫效果。下面簡(jiǎn)單介紹一下OPhone里面動(dòng)畫效果的用法,只是用的xml的方式。

定義動(dòng)畫效果的xml,在res/anim/fade_in.xml

  1. <alpha  
  2. android:fromAlpha="0.1"  
  3. android:toAlpha="1.0"  
  4. android:duration="3000"  
  5. android:interpolator="@android:anim/accelerate_decelerate_interpolator"  
  6. />  

定義ImageView,加載動(dòng)畫

  1. songPicture = (ImageView)findViewById(R.id.songPicture);   
  2. inAnimation = AnimationUtils.loadAnimation(this,R.anim.fade_in);  

當(dāng)更新專輯封面時(shí),開(kāi)始動(dòng)畫

  1. songPicture.setImageBitmap(pic);   
  2. songPicture.startAnimation(inAnimation);  

AndroidManifest里面的設(shè)置

permission

需要的permission,主要有網(wǎng)絡(luò),存儲(chǔ),監(jiān)聽(tīng)手機(jī)和網(wǎng)絡(luò)狀態(tài)。

  1. <uses-permission android:name="android.permission.INTERNET" />   
  2. <uses-permission android:name="android.permission.WRITE_OWNER_DATA" />   
  3. <uses-permission android:name="android.permission.READ_OWNER_DATA" />   
  4. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>   
  5. <uses-permission android:name="android.permission.READ_PHONE_STATE"/>   
  6. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>  

要求sdk最低版本

  1. <uses-sdk android:minSdkVersion="3"/>  

設(shè)置應(yīng)用為單實(shí)例模式

  1. <application ... android:launchMode="singleInstance" ...>  

設(shè)置Service

禁止別的應(yīng)用使用該Service,需要將里面的exported設(shè)為false,另外設(shè)置Service的進(jìn)程名字后綴為":radio"。

  1. <service android:name=".RadioService" android:exported="false" android:process=":radio" >   
  2.     <intent-filter>   
  3.         <action android:name="..." />   
  4.     </intent-filter></service>  

其他技巧

MediaPlayer的prepare

MediaPlayer有2種prepare方式,建議使用異步的prepare,然后在OnPreparedListener里面監(jiān)聽(tīng),當(dāng)準(zhǔn)備好時(shí)處理。

  1. mplayer.setOnPreparedListener(prepareListener); //設(shè)置listener   
  2. ...   
  3. mplayer.reset();   
  4. mplayer.setDataSource(song.url); //設(shè)置歌曲url   
  5. mplayer.prepareAsync();    
  6. ...   
  7. private MediaPlayer.OnPreparedListener prepareListener = new MediaPlayer.OnPreparedListener(){   
  8.     
  9.     public void onPrepared(MediaPlayer mp){   
  10.         mp.start(); //開(kāi)始播放   
  11.         //通知Player更新歌曲時(shí)間   
  12.         sendBroadcast(new Intent(Consts.INTENT_UPDATE_SONG_TIME));   
  13.     }   
  14. };  

查看有沒(méi)有可用的網(wǎng)絡(luò)連接

下面是監(jiān)聽(tīng)網(wǎng)絡(luò)連接狀態(tài)的函數(shù),這里只是檢查有沒(méi)有可用的連接。其實(shí)還可用根據(jù)不同連接的類型來(lái)使用不同的策略,比如用WIFI的時(shí)候就用高音質(zhì)的音樂(lè),而當(dāng)用3G/2G上網(wǎng)時(shí)就用比較低的音樂(lè)。

  1. public boolean isNetworkAvailable() {    
  2.     Context context = getApplicationContext();   
  3.     ConnectivityManager connectivity =    
  4.         (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);   
  5.     nkify.addLinksif (connectivity == null) {       
  6.         return false;   
  7.     } else {    
  8.         NetworkInfo[] info = connectivity.getAllNetworkInfo();       
  9.         if (info != null) {           
  10.             for (int i = 0; i < info.length; i++) {              
  11.                 if (info[i].getState() == NetworkInfo.State.CONNECTED) {                 
  12.                     return true;    
  13.                 }           
  14.             }        
  15.         }    
  16.     }      
  17.     return false;   
  18. }  

監(jiān)聽(tīng)手機(jī)狀態(tài)

利用PhoneStateListener來(lái)監(jiān)聽(tīng)手機(jī)是否收到電話。這里沒(méi)有處理打電話的問(wèn)題是因?yàn)槲矣X(jué)得,你如果要播電話的時(shí)候一定會(huì)想先把電臺(tái)關(guān)了的,呵呵。

  1. private class TeleListener extends PhoneStateListener{   
  2.     public void onCallStateChanged(int state, String incomingNumber){    
  3.         super.onCallStateChanged(state, incomingNumber);   
  4.         switch (state){   
  5.             case TelephonyManager.CALL_STATE_IDLE:   
  6.                 startPlay(); //當(dāng)掛斷電話時(shí),繼續(xù)播放   
  7.                 break;   
  8.             case TelephonyManager.CALL_STATE_OFFHOOK:   
  9.                 doStop(); //當(dāng)有電話在等時(shí),暫停音樂(lè)   
  10.                 break;   
  11.             case TelephonyManager.CALL_STATE_RINGING:   
  12.                 doStop(); //當(dāng)有電話進(jìn)來(lái)時(shí),暫停音樂(lè)   
  13.                 break;   
  14.         }   
  15.     }   
  16. }  

橫豎屏轉(zhuǎn)換

默認(rèn)橫豎屏轉(zhuǎn)換是會(huì)把你的Activity關(guān)掉重新啟動(dòng)的,但你可以在你的manifest里面設(shè)置Activity的屬性configChanges來(lái)自己處理,也可以在手機(jī)橫豎屏轉(zhuǎn)換的時(shí)候來(lái)加載不同的UI layout。

  1. <activity android:name=".RadioPlayer" ....    
  2.     android:configChanges="orientation|keyboardHidden|navigation" >   
  3. ...   
  4. </activity>  

然后在onConfigurationChanged來(lái)處理加載不同的layout。

  1. public void onConfigurationChanged(Configuration newConfig){   
  2.     if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){   
  3.         setContentView(R.layout.landscape);   
  4.     }else{   
  5.         setContentView(R.layout.portrait);   
  6.     }      
  7. }  

結(jié)束語(yǔ)

本文的例子豆瓣電臺(tái)還在開(kāi)發(fā)中,在做UI的美化以及一些改進(jìn)用戶體驗(yàn)的特性,會(huì)在不遠(yuǎn)的將來(lái)發(fā)布,還請(qǐng)大家關(guān)注。

就到這里,本文是以O(shè)MS平臺(tái)的網(wǎng)絡(luò)應(yīng)用為例,講一個(gè)web開(kāi)發(fā)人員在學(xué)習(xí)OMS平臺(tái)的應(yīng)用開(kāi)發(fā)時(shí)會(huì)遇到的典型問(wèn)題的解決方案,呵呵。希望對(duì)大家有所幫助。


本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
IntentService 源碼分析 - Android - 老翟的倉(cāng)庫(kù)
[z]Activity與Service通信
Android Service學(xué)習(xí)之IntentService 深入分析
大談android安全2——Activity劫持的防范程序
Android中Messenger的使用
Android Service
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服