Android自定义音乐播放器

一:首先介绍用了哪些Android的知识点:

1 MediaPlayer工具来播放音乐

2 Handle。因为存在定时任务(歌词切换,动画,歌词进度条变换等)需要由Handle来处理Ui相关内容

3 动态权限申请(该应用程序读取本地歌曲,并且设置音质相关属性)且这两个权限在Android6.0后都需要动态申请

4 手势控制 (左划和右划需要满足一定条件后可以进行切歌)

5 Service服务 (启动Service 绑定Service 前台Service)

6 BroadcastReceiver 广播,Service与Activity,Activity与Activity存在动态交互,需要广播实现

7 基本的重写View能力和Intent交互数据的能力

8 Animation动画 图片旋转 歌词更新


二:实现过程(简要步骤,下面会详细讲解)

1 先编写MusicInfo工具类。因为我们是从手机内存中去读取音乐的相关信息,那么读出的数据该存储到MusicInfo工具类集合中。

2 先申请权限,然后再去手机内存将音乐及其相关的信息读出来 ,用一个ListView容器去装载所有的本地音乐

PS:到了这里基本的音乐信息列表已经有了   这也是我们的主界面(音乐列表界面) 即: 展示音乐/歌曲列表;


3 这时候先不方去实现播放这类的功能,我们先去处理歌词

这里说明一下。一个歌词文件(.Lrc)里面内容格式如下

(张卫健--真英雄)

可以发现他由时间戳和歌词内容两部分组成。有了这个信息后。编写LrcContent工具类,用于记录歌词内容和歌词时间。然后去手机里面寻找歌曲对应的歌词文件,将其编码,读出,装载为LrcContent集合。

4 编写Service类,Service主要用来处理:音乐播放,前台服务。在播放状态改变的时候与播放音乐的Activity进行通信。该Service由主界面启动,后面的Activity绑定即可

5 编写播放音乐的Activity类(MusicPlay)。当我们从主界面(音乐列表界面)点击了一首具体的音乐时,就会调转到该Activity,所有首先,主界面Activity需要传递一些信息给该Activity。

(1) MusicInfo工具类集合。即手机中所有的音乐信息

(2)当前点击的歌曲,传递位置(position)即可

好,现在我们播放音乐的Activity有了所有的音乐信息,还有当前需要播放的歌曲位置。因为需要前台服务,所以我们把音乐播放的控制权交给Service,我们去绑定服务,然后把所有的音乐信息,还有当前需要播放的歌曲位置都传递给Serivce,Service来控制播放音乐。

6 好了,现在我们的程序可以播放音乐了,我们再来一步步完善细节,歌词同步,该功能自定义View实现,最后显现在播放音乐的Activity中。注意Mediaplayer有一个重要属性:

mediaPlayer.getCurrentPosition()。该方法会返回当前播放时间,不过返回的时候时毫秒(重)。

自定义View(LrcView 显示歌词),该View中除了传统的自定义View需要的OnDraw之类的方法外,还需要获取第三步中的LrcContent集合,有了这个我们就有了所有的歌词内容和歌词相应的时间,那么同步如何实现呢?音乐最终该View要显示在播放音乐的Activity(MusicPlay)中,我们去MusicPlay的布局文件申明该View,然后在MusicPlay中编写一个定时器,可以设定每一秒启动一次,定时器发送消息,在Handle中接受消息,处理消息。Handle中我们需要:获取歌曲当前播放时间,根据当前播放时间去LrcContent集合中寻求匹配的歌词。用invalidate()方法,通知自定义View重绘,来同步更新歌词

7 我们的音乐播放器还差一个重要的东西,音乐控制器部分。这部分需要来控制播放上一首,下一首,播放/暂停,音乐进度拖动,音量设置。

该部分不难,所以再这里不详细讲。

三:效果图  因为完整录制的GIF太大,传不上来所以分批处理




四:代码精讲 代码量也不很大,但是全贴出来挨着讲又影响阅读。所以部分节选和重要知识点一并讲解。

PS:源码中含有大量System.out.println("XXXX");语句。个人比较偏爱的一种测试方式。。应该不干扰阅读,忘见谅

1 权限获取,我们要做的第一件事就是去内存读取音乐相关信息,那么我们就需要获得相关的权限,从Android6.0开始部分权限不仅需要在AndroidManifest.xml文件中声明,还需要在运行程序的时候动态获取.这里以读取存储权限为例:

首先在AndroidManifest.xml中定义

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

在Java业务代码中先判断是否已经有权限,有权限就不再申请,没有就申请权限

//首先检查自身是否已经拥有相关权限,拥有则不再重复申请int check = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ;    //没有相关权限    if (check != PackageManager.PERMISSION_GRANTED)    {        //申请权限  STORGE_REQUEST = 1        ActivityCompat.requestPermissions(this , new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE} ,STORGE_REQUEST);    }else {    //已有权限的情况下可以直接初始化程序    init();}

当我们申请权限后,去判断用户是否给与了相关权限,如果赋予了就可以做我们的事情了

/*申请权限处理结果 */@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {     super.onRequestPermissionsResult(requestCode, permissions, grantResults);     switch (requestCode)     {         case STORGE_REQUEST :             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)             {                 //完成程序的初始化                 init();                 System.out.println("程序申请权限成功,完成初始化") ;             }             else {                 System.out.println("程序没有获得相关权限,请处理");             }             break ;     }}

关于权限这部分:有兴趣的可以看下参考下这篇博客

Android 6.0 运行时权限管理最佳实践

2 有了权限,我们就可以从手机中去读取音乐相关信息了,这里讲解下如何去读取信息

(1) 我们需要先创建一个存储音乐信息MusicInfo工具类  

注意:代码没贴完,还有属性的get和set方法没列出

public class MusicInfo implements Serializable {    private  int _id = - 1;       //音乐标识码    private  int duration = -1 ;   //音乐时常    private String artist = null ;    //音乐作者    private String musicName= null ; //音乐名字    private String album = null ;   //音乐文件专辑    private String title = null ;  //音乐文件标题    private int size  ;   //音乐文件的大小  返回byte大小    private String data ;  //获取文件的完整路径    private String album_id ; //实际存储为音乐专辑团片}   


(2)读取音乐信息。

Android中的音乐信息存储再手机数据库中,那么我们就需要去访问数据库。这里介绍下如何访问数据库

首先要知道基本数据库查询操作,返回Cursor对象

public final Cursor query(Uri uri , //查询路径

                            String[] projection , //查询指定的列

                            String selection, //查询条件

                            String[] selectionArgs, //查询参数

                           String sortOrder } //查询结果的排序方式

 那么就有了我们查询音乐的操作。

   
//申明ContentResolver对象,用于访问系统数据库private ContentResolver contentResolver ;//用于装载MusicInfo对象private List musicInfos ;
//获取系统的ContentResolvercontentResolver = getContentResolver() ;//从数据库中获取指定列的信息mCursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI ,        new String[] {MediaStore.Audio.Media._ID ,                MediaStore.Audio.Media.TITLE , MediaStore.Audio.Media.ALBUM ,                MediaStore.Audio.Media.ARTIST , MediaStore.Audio.Media.DURATION ,                MediaStore.Audio.Media.DISPLAY_NAME , MediaStore.Audio.Media.SIZE ,                MediaStore.Audio.Media.DATA , MediaStore.Audio.Media.ALBUM_ID } , null ,null ,null) ;
   

我们已经查询出了所有的音乐信息,并且存储在mCursor对象中,现在我们需要把音乐信息装载到musicInfos工具类集合中。

但是注意,音乐专辑图片需要特殊处理:我们这是只查询出了音乐专辑id   MediaStore.Audio.Media.ALBUM_ID 我们需要根据音乐专辑id再去查询音乐专辑图片,用该图片来作为音乐图片。

/*    获取本地音乐专辑的图片 */private String getAlbumArt(int album_id){    String UriAlbum = "content://media/external/audio/albums" ;    String projecttion[] =  new String[] {"album_art"} ;    Cursor cursor = contentResolver.query(Uri.parse(UriAlbum + File.separator +Integer.toString(album_id)) ,            projecttion , null , null , null);    String album = null ;    if (cursor.getCount() > 0 && cursor.getColumnCount() > 0)    {        cursor.moveToNext() ;        album = cursor.getString(0) ;    }    //关闭资源数据    cursor.close();    return album ;}

这时还可能出现一个问题,可能存在本地专辑音乐图片不存在,所以我们要进行判断,没有专辑图片就使用默认的图片。好了,现在我们就可以装载音乐信息了

musicInfos = new ArrayList<>() ;for (int i = 0 ; i < mCursor.getCount() ; i++){    Map map = new HashMap<>() ;    MusicInfo musicInfo = new MusicInfo() ;    //列表移动    mCursor.moveToNext() ;    //将数据装载到List    musicInfo.set_id(mCursor.getInt(0));    musicInfo.setTitle(mCursor.getString(1));    musicInfo.setAlbum(mCursor.getString(2));    musicInfo.setArtist(mCursor.getString(3));    musicInfo.setDuration(mCursor.getInt(4));    musicInfo.setMusicName(mCursor.getString(5));    musicInfo.setSize(mCursor.getInt(6));    musicInfo.setData(mCursor.getString(7));      //将数据装载到List>中    //获取本地音乐专辑图片    String MusicImage = getAlbumArt(mCursor.getInt(8)) ;    //判断本地专辑的图片是否为空    if (MusicImage == null)    {        //为空,用默认图片        map.put("image" , String.valueOf(R.mipmap.timg)) ;        musicInfo.setAlbum_id(String.valueOf(R.mipmap.timg));    }else    {        //不为空,设定专辑图片为音乐显示的图片        map.put("image" , MusicImage) ;        musicInfo.setAlbum_id(MusicImage);    }   // musicInfo.setAlbum_id(mCursor.getInt(8));    musicInfos.add(musicInfo) ;

3 MediaPlayer重要方法:MediaPlayer用来指定音乐文件和播放相关动作,所以我们需要掌握与音乐播放相关的方法。

方法: getCurrentPosition() 

解释:返回 Int, 得到当前播放位置  毫秒单位时间

方法: getDuration() 

解释:返回 Int,得到文件的时间 

方法:isLooping() 

解释:返回 boolean ,是否循环播放

方法:isPlaying() 

解释:返回 boolean,是否正在播放

方法:pause() 

解释:无返回值 ,暂停

方法:release() 

解释:无返回值,释放 MediaPlayer 对象

方法:reset() 

解释:无返回值,重置 MediaPlayer 对象

方法:seekTo(int msec) 

解释:无返回值,指定播放的位置(以毫秒为单位的时间) 

方法:setDataSource(String path) 

解释:无返回值,设置多媒体数据来源【根据 路径】

方法:setLooping(boolean looping) 

解释:无返回值,设置是否循环播放 

方法:start() 

解释:无返回值,开始播放

方法:stop() 

解释:无返回值,停止播放

方法:setVolume(float leftVolume, float rightVolume) 

解释:无返回值,设置音量 

事件:setOnCompletionListener(MediaPlayer.OnCompletionListener listener) 

解释:监听事件,网络流媒体播放结束监听

事件:setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) 

解释:监听事件,网络流媒体的缓冲监听

事件:setOnErrorListener(MediaPlayer.OnErrorListener listener) 
解释:监听事件,设置错误信息监听

4 歌词处理  说下思路把:首先根据歌曲去内存寻找对应的歌词文件,找到后解码为歌词内容和歌词时间,并且把歌词内容和个时间装载到歌词信息类集合中。然后提供一个外部访问的方法用于向外部输出歌词信息

// 注:LrcContent是歌词处理类 其中包含两个参数,歌词时间和歌词内容
public class LrcProcess {    //所有需要处理的歌词对象    private List lrcList;    //一个歌词对象    private LrcContent mLrcContent;    /*    构造函数完成对象的初始化     */    public LrcProcess() {        lrcList = new ArrayList<>();    }     /*    *从内存中读取歌词文件,并转换为String对象输出    */     public String readLrc(String path)     {         //用StringBuild来存储歌词内容         StringBuilder sb = new StringBuilder() ;         //获取歌词文件  因为传入的文件为MusicInfo类中的Data内容,其文件为mp3,需要更换为lrc文件         File f = new File(path.replace(".mp3" , ".lrc")) ;         try{             //通过文件流对象来获取文件内容并且导入歌词内容对象集合中(lrcList)             FileInputStream inputStream = new FileInputStream(f) ;             InputStreamReader streamReader = new InputStreamReader(inputStream , "utf-8") ;             BufferedReader bufferedReader = new BufferedReader(streamReader) ;             String tempStr = "" ;             while((tempStr = bufferedReader.readLine()) != null)             {                 //实现字符替换                 tempStr = tempStr.replace("[" , "") ;                 tempStr = tempStr.replace("]" , "@") ;                 //根据@分号对文件分离                 String[] splitData = tempStr.split("@") ;               //  System.out.println("THE TEMP STR IS " + splitData[0]) ;                 if (splitData.length > 1)                 {                     //新建歌词内容对象                     mLrcContent = new LrcContent();                     //设置歌词文本内容                     mLrcContent.setLrcStr(splitData[1]);                     //设置歌词时间                     int lrcTime = timeToStr(splitData[0]) ;                     mLrcContent.setLrcTime(lrcTime);                     //添加到列表                     lrcList.add(mLrcContent);                   //  System.out.println("录入歌词成功") ;                 }else {                    // System.out.println("录入歌词失败") ;                 }             }         }catch (FileNotFoundException e)         {             sb.append("木有歌词文件,赶紧去下载!...");         } catch (UnsupportedEncodingException e) {             e.printStackTrace();             sb.append("木有读取到歌词哦!");         } catch (IOException e) {             e.printStackTrace();             sb.append("木有读取到歌词哦!");         }         return sb.toString() ;     }     /*     * 对歌词文件lrc中的时间内容进行转码     * [00:02.32]陈奕迅   时间分别代表分,秒,毫秒     * [00:03.43]好久不见      */     public int timeToStr(String timeStr)     {         timeStr = timeStr.replace(":" , ".") ;         timeStr = timeStr.replace("." , "@") ;         String []splitTime = timeStr.split("@") ;         //分离出分, 秒, 毫秒          int minute = Integer.parseInt(splitTime[0]) ;         int second = Integer.parseInt(splitTime[1]) ;         int millisSecond = Integer.parseInt(splitTime[2]) ;         int time = (minute * 60 + second) * 1000 + millisSecond ;         return time ;     }     /*     提供一个外界方法歌词对象集合的方法      */     public  List getLrcList()     {         return lrcList ;     }}
//实例化歌词处理对象   Activity中调用方法    //musicInfosList为封装好的所有音乐信息类,get(position)可以获取当前音乐信息,getData获取音乐路径     LrcProcess  mLrcProcess = new LrcProcess() ;mLrcProcess.readLrc(musicInfosList.get(position).getData()) ;

这里再补一个内容:通常经常下载的歌曲都是没有歌词文件的,所以我们需要自己去下载歌词文件,注意该程序中你需要把歌词文件和音乐文件放在一个地方,并且歌词文件和音乐文件前缀相同只是将.mp3改为了.lrc。我用的是网易云音乐,这里顺便推荐一个网易云下载歌词的网址,大神们各种推荐供你选择

知乎:网易云音乐怎么下载歌词?

5 歌曲播放界面

看图分析:

(1)标题动画


这两个TextView的布局代码就不再列出了。动画效果主要是继承了Animation类自定实现。

public class TextAnimation extends Animation {    private float currentX ;  //指定X    private float currentY ;  //指定Y    //定义持续时间    private int duration ;    //设定Camera    private Camera camera = new Camera() ;    public TextAnimation(float x , float y ,int duration)    {        currentX = x ;        currentY = y ;        this.duration = duration ;        System.out.println("THE X IS " + currentX + "\nTHE Y IS " + currentY) ;    }    @Override    public void initialize(int width, int height, int parentWidth, int parentHeight) {        super.initialize(width, height, parentWidth, parentHeight);        //设置持续时间        setDuration(duration);        //设置动画结束后保留        setFillAfter(true);        //设置变换速度        setInterpolator(new AccelerateDecelerateInterpolator());  //减速        //setInterpolator(new AccelerateInterpolator());        //加速 默认情况        //setInterpolator(new LinearInterpolator());          //匀速    }    @Override    protected void applyTransformation(float interpolatedTime, Transformation t) {        super.applyTransformation(interpolatedTime, t);        /*        保存         */        camera.save();        //根据interpolatedTime来控制X Y Z上的偏移        camera.translate(10.0f - 10.0f * interpolatedTime ,                30.0f  - 30.0f * interpolatedTime ,                80.0f - 80.0f * interpolatedTime);        //根据interpolatedTime在X轴做角度变换        camera.rotateX(360 * interpolatedTime);        //根据interpolatedTime在Y轴做角度变换        camera.rotateY(360 * interpolatedTime);        //获取Transformation参数封装的matrix对象        camera.getMatrix(t.getMatrix());        t.getMatrix().preTranslate(-currentX / 4 , -currentY / 4) ;        t.getMatrix().postTranslate(currentX , currentY) ;        /*        如果存在保存的状态,就恢复         */        camera.restore();    }}
 initialize(int width, int height, int parentWidth, int parentHeight)中,width和height代表指定播放动画的View空间宽高,parentWidth和parentHeight代表该View控件所在的父控件宽高。可以在该方法中获取View的宽和高,但是我在这里面主要是完成的初始化

applyTransformation()方法是动画具体的实现方法,在系统绘制动画时会反复调用这个方法,每调用一次applyTransformation()方法,其中的interpolatedTime参数都会改变一次,值从0到1递增,当interpolatedTime的值为1时则动画结束。Transformatio类是一个变换的矩阵,通过改变该矩阵就可以实现各种复杂的效果。
PS:更多的相关的动画知识可以阅读官方文档,或者其它博文,书籍,网络视频获取。

Activity调用该动画方法:

/*获取并且设置音乐的标题和歌手。并添加动画来显示 */
MusicArtist = (TextView) findViewById(R.id.MusicArtist) ;MusicName = (TextView) findViewById(R.id.MusicName) ;MusicName.setText(musicInfosList.get(position).getTitle());MusicArtist.setText(musicInfosList.get(position).getArtist());//动画效果MusicArtist.setAnimation(new TextAnimation(0 , 0 , 2000));MusicName.setAnimation(new TextAnimation(0 , 0 , 2000));
(2)音乐专辑图片处理



1,圆形图片

 需要用到Shader

 Shader的使用步骤:
1. 构建Shader对象
2. 通过Paint的setShader方法设置渲染对象
3.设置渲染对象
4.绘制时使用这个Paint对象

public class XCRoundImageView extends android.support.v7.widget.AppCompatImageView{    private Paint mPaintBitmap = new Paint(Paint.ANTI_ALIAS_FLAG);    private Bitmap mRawBitmap;    private BitmapShader mShader;    private Matrix mMatrix = new Matrix();    public XCRoundImageView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onDraw(Canvas canvas) {        //获取原生Bitmap位图        Bitmap rawBitmap = getBitmap(getDrawable());        if (rawBitmap != null){            //获取图片宽和高            int viewWidth = getWidth();            int viewHeight = getHeight();            //由于要变换为圆形,在变换过程中取边长相对小的为基准            int viewMinSize = Math.min(viewWidth, viewHeight);            float dstWidth = viewMinSize;            float dstHeight = viewMinSize;            //如果是第一次绘制            if (mShader == null || !rawBitmap.equals(mRawBitmap)){                mRawBitmap = rawBitmap;                /*                BitmapShader是Shader的子类,可以通过Paint.setShader(Shader shader)进行设置、                这里我们只关注BitmapShader,构造方法:                mBitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);                参数1:bitmap                参数2,参数3:TileMode;                TileMode的取值有三种:                CLAMP 拉伸                REPEAT 重复                MIRROR 镜像                如果大家给电脑屏幕设置屏保的时候,如果图片太小,可以选择重复、拉伸、镜像;                重复:就是横向、纵向不断重复这个bitmap                镜像:横向不断翻转重复,纵向不断翻转重复;                拉伸:这个和电脑屏保的模式应该有些不同,这个拉伸的是图片最后的那一个像素;横向的最后一个横行像素,不断的重复,纵项的那一列像素,不断的重复;                */                mShader = new BitmapShader(mRawBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);            }            if (mShader != null){                /*                 void setLocalMatrix(Matrix localM);                 设置shader的本地矩阵,如果localM为空将重置shader的本地矩阵。                 */                mMatrix.setScale(dstWidth / rawBitmap.getWidth(), dstHeight / rawBitmap.getHeight());                mShader.setLocalMatrix(mMatrix);            }            mPaintBitmap.setShader(mShader);            float radius = viewMinSize / 2.0f;            canvas.drawCircle(radius, radius, radius, mPaintBitmap);        } else {            super.onDraw(canvas);        }    }    private Bitmap getBitmap(Drawable drawable){        if (drawable instanceof BitmapDrawable){            return ((BitmapDrawable)drawable).getBitmap();        } else if (drawable instanceof ColorDrawable){            Rect rect = drawable.getBounds();            int width = rect.right - rect.left;            int height = rect.bottom - rect.top;            int color = ((ColorDrawable)drawable).getColor();            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);            Canvas canvas = new Canvas(bitmap);            canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));            return bitmap;        } else {            return null;        }    }}

PS:作者当时也是搬运的,只是补充了部分注释。内容可能有点复杂,需要多实践,建议参考资料文档

自定义控件之 圆形 / 圆角 ImageView

自定义控件三部曲之绘图篇(十八)——BitmapShader与望远镜效果

2,图片旋转

首先定义旋转动画 :image_rotate.xml

<?xml version="1.0" encoding="utf-8"?>    

然后在Java业务代码中设定定时器来启动动画

/*该Timer用于实现:音乐播放界面图片旋转动画 */MyHandle2 handle2 = new MyHandle2() ;new Timer().schedule(new TimerTask() {    @Override    public void run() {        handle2.sendEmptyMessage(0x112) ;    }}, 0 ,8000);

最后再Handle中接收消息,并且启动动画

public class MyHandle2 extends  Handler{    @Override    public void handleMessage(Message msg) {        if ((msg.what == 0x112))        {            //设置图片旋转            MusicImage.setAnimation(AnimationUtils.loadAnimation(MusicPlay.this , R.anim.image_rotate));        }    }}



五:下载地址github

https://github.com/547291213/MusicPlayer


六:参考资料

  •  Android音乐播放器(一):搜索手机存储的音乐
  •  Android 6.0 运行时权限管理最佳实践
  •  android 完美获取音乐文件中的专辑图片并显示
  •  Android开发笔记(一百二十六)自定义音乐播放器
  •  Android应用开发--MP3音乐播放器滚动歌词实现
  •  Android实现音乐示波器、均衡器、重低音和音场功能
  •  自定义控件之 圆形 / 圆角 ImageView 
  • 【Android】Service前台服务的使用
  •  官方开发文档

七:总结

第一次写这么长文的博客,也没想过邀功。不过真心希望您再阅读了这篇博文,并且有所建议和收获后留下您宝贵的评论。谢谢。

PS:转载请注明 https://blog.csdn.net/qq_29989087/article/details/80206290





更多相关文章

  1. 巨佬Jake Wharton谈Android对Java 8的支持
  2. Android手机安全软件的恶意程序检测靠谱吗--LBE安全大师、腾讯手
  3. Android(安卓)HAL实现的三种方式(2) - 基于Service的HAL设计
  4. Android(安卓)Fragment完全解析,关于碎片你所需知道的一切
  5. Android(安卓)Activity生命周期详解
  6. Android消息处理机制:Handler|Message
  7. Android(安卓)LayoutInflater原理分析,带你一步步深入了解View(一
  8. Android编程之客户端通过socket与服务器通信的方法
  9. android反编译和防止反编译的方法

随机推荐

  1. Android(安卓)Studio 1.0 苹果电脑安装配
  2. 将要更新到android 4.0的手机列表
  3. Android(安卓)系统概要 ——《第一行代码
  4. android访问服务器端上传及服务器端接收
  5. Android的View类介绍-android的学习之旅(
  6. android CTS测试
  7. Qt on Android:将Qt调试信息输出到logcat
  8. 小熊android学习总结:Linux内核怎样启动An
  9. Android(安卓)call setting 源码分析 从
  10. 最封闭的开源系统:话说 Android(安卓)的八