转载请注明出处:http://www.blogjava.net/zh-weir/archive/2010/01/24/310617.html


2010.03.27SeeJoPlayer v1.2.0 beta版:

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)下载SeeJoPlayer v1.2.0 betaAndroid平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)下载SeeJoPlayer v1.2.0 beta源码


更新说明:

1、完美支持android1.5、android1.6、android2.0、android2.01、android2.1平台;
2、完美支持320×480、480×800、480×854等各种分辨率(自适应屏幕分辨率);
3、支持在线音视频播放,支持URL input和从浏览器调用SeeJoPlayer播放器播放在线音视频;
4、自动转为横屏播放,为用户提供更好的观看体验;
5、修改了没有SD卡程序出错的Bug;
6、美化了视频播放列表和操作说明的界面。

补充图片:

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)


URL输入功能

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)


浏览器中调用SeeJoPlayer播放


感谢大家对SeeJoPlayer的大力支持!希望新版本能带给大家更好的体验!


2010.01.24SeeJoPlayer v1.0.0版:

SeeJoPlayer是我利用业余时间开发的一款免费的视频播放器。主要是现在在网上似乎找不到一个Android平台下的界面美观一点的视频播放器。而作为智能手机操作系统的Android,没有一个像样一点的视频播放器,岂不糗大了。所以,我就写了这么一个砖头并开出源码,希望能引出高手们的美玉来吧!


Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)下载APK程序Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)下载源代码Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)转到eoeMarket发布页

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)


第一部分:功能介绍


SeeJoPlayer的优点主要在相对还算美观的界面和便捷的交互操作上。先说操作吧,它支持:

1、全屏切换: 双击屏幕
2、播放/暂停:长按屏幕
3、静音/恢复:长按音量按钮
4、播放列表: 控制面板最右边的按钮(暂不支持编辑功能)
5、音量调节: 单击音量按钮,在弹出的音量显示区域触摸改变音量


这些操作和PC上的播放器较为类似,希望大家能用得习惯。

至于界面的话,多说无益,直接上图吧:

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

横屏

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

竖屏

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

全屏

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

非全屏

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

播放列表

Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

介绍说明

好了。功能介绍部分到此为止了。如果您觉得这个软件还行的话,欢迎下载使用!


Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)下载APK程序Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)下载源代码Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)转到eoeMarket发布页



第二部分:源码解析


SeeJoPlayer不是一个完美的作品,可以说,它在很多地方都不尽如人意。当然一个完美的作品,也不是我写这款播放器的目的。我只是希望以此为引,结合大家共同的智慧开发出一款真正完美强大的Android平台下的国产视频播放器出来。

SeeJoPlayer有许多不足之处,例如,它只支持系统默认的视频格式,因为它使用系统默认的解码器。这,一方面是因为如果通过软解码的话,播放视频的效率会很受影响,另外最主要的原因当然还是个人水平、精力有限,没办法接着往下做了。如果大家觉得这份代码还多少有些参考价值的话,不妨拿去用。只是希望当你们以此为参考,开发出真正强大的播放器出来的时候,别忘了如果能开放源码的话,一定开放出来。毕竟开源软件就好比能够进化的物种,提供你的DNA出来,让我们共同的软件变得越来越完美吧!

好了,废话不说了。播放器的全部源码本文中已经提供了下载地址。下面,我就其中我觉得可能值得关注的地方做一些解释。

一、VideoView与视频比例缩放:

以前在论坛上也看到有人问过如何实现视频按比例缩放的问题。的确,如果仅仅使用VideoView可能达不到我们想要达到的效果。这就需要我们对VideoView做一些改动,简单的说就是另外写一个类似VideoView的类出来(庆幸Android是开源的)。

我们可以很方便的获得VideoView的源代码,最简单的方法是直接在GoogleCodeSearch上找“VideoView.java”。所以重写VideoView的过程其实只是在原来的基础上进行一些修改而已,并非一个很麻烦的工作。为什么Android自带的VideoView会保持视频的长宽比而不能让我们很方便的自定义比例呢?我猜想可能Google做Android也是一个很仓促的工程,许多代码并没有考虑得太成熟。

VideoView的源码中有这样一段代码:

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)@Override
2Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
3Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@@","onMeasure");
4Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)intwidth=getDefaultSize(mVideoWidth,widthMeasureSpec);
5Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)intheight=getDefaultSize(mVideoHeight,heightMeasureSpec);
6Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)if(mVideoWidth>0&&mVideoHeight>0){
7Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)if(mVideoWidth*height>width*mVideoHeight){
8Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@","imagetootall,correcting");
9Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)height=width*mVideoHeight/mVideoWidth;
10Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}
elseif(mVideoWidth*height<width*mVideoHeight){
11Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@","imagetoowide,correcting");
12Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)width=height*mVideoWidth/mVideoHeight;
13Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}
else{
14Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@","aspectratioiscorrect:"+
15Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//width+"/"+height+"="+
16Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//mVideoWidth+"/"+mVideoHeight);
17Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}

18Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}

19Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@@@@@@@@","settingsize:"+width+'x'+height);
20Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)setMeasuredDimension(width,height);
21Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}

22Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

这就是为什么长宽比不能改变的原因了。因为在OnMeasure的时候,就对这个长宽比进行了处理。

我们把其中处理的代码屏蔽掉,视频大小就可以随着VideoView的长宽改变而改变了。

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)@Override
2Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
3Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@@","onMeasure");
4Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)intwidth=getDefaultSize(mVideoWidth,widthMeasureSpec);
5Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)intheight=getDefaultSize(mVideoHeight,heightMeasureSpec);
6Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)/*if(mVideoWidth>0&&mVideoHeight>0){
7Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)if(mVideoWidth*height>width*mVideoHeight){
8Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@","imagetootall,correcting");
9Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)height=width*mVideoHeight/mVideoWidth;
10Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}elseif(mVideoWidth*height<width*mVideoHeight){
11Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@","imagetoowide,correcting");
12Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)width=height*mVideoWidth/mVideoHeight;
13Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}else{
14Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@","aspectratioiscorrect:"+
15Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//width+"/"+height+"="+
16Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//mVideoWidth+"/"+mVideoHeight);
17Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}
18Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}*/

19Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//Log.i("@@@@@@@@@@","settingsize:"+width+'x'+height);
20Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)setMeasuredDimension(width,height);
21Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}


二、视频控制菜单与播放界面的层次问题:

看到过一些别人写的视频播放器,其中有一些朋友老是简简单单的将VideoView和控制界面放在一个LinearLayout中。这样随着控制界面的出现与否,VideoView会随之改变长宽,给人的体验并不很好。所以,我认为VideoView和控制界面最好不要放在同一个层次上。不要偷懒,使用一个FrameLayout或者PopupWindow就可以解决这个问题。例如,我就简简单单地使用了PopupWindow,这个具体实现上,就百花争鸣吧。

三、视频文件扫描:

视频文件的扫描,现在想来主要有两种方式:

第一种就是直接读取媒体库中的视频文件数据库。当Android启动的时候,系统会自动扫描sdcard,并为媒体文件建立(或者更新)数据库。我们可以通过对应的URI来访问数据库,从而得到视频文件的列表:

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)privateUrivideoListUri=MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
2Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
3Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
4Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
5Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Cursorcursor=getContentResolver().query(videoListUri,newString[]{"_display_name","_data"},null,null,null);
6Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)intn=cursor.getCount();
7Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)cursor.moveToFirst();
8Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)LinkedList<MovieInfo>playList2=newLinkedList<MovieInfo>();
9Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)for(inti=0;i!=n;++i){
10Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)MovieInfomInfo=newMovieInfo();
11Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)mInfo.displayName=cursor.getString(cursor.getColumnIndex("_display_name"));
12Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)mInfo.path=cursor.getString(cursor.getColumnIndex("_data"));
13Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)playList2.add(mInfo);
14Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)cursor.moveToNext();
15Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}


这种方法可能是最有效率的了,不过不知为何,媒体库中似乎没有扫描进本身支持的3GP视频格式(也可能我这里是一个特例) 。不过,正是因为这个原因,我才想到有可能需要另外一种最基本的扫描文件系统的方法来扫描视频文件。这就是文件系统的遍历:

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)privatevoidgetVideoFile(finalLinkedList<MovieInfo>list,Filefile){
2Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
3Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)file.listFiles(newFileFilter(){
4Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
5Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)@Override
6Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)publicbooleanaccept(Filefile){
7Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//TODOAuto-generatedmethodstub
8Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Stringname=file.getName();
9Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)inti=name.indexOf('.');
10Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)if(i!=-1){
11Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)name=name.substring(i);
12Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)if(name.equalsIgnoreCase(".mp4")||name.equalsIgnoreCase(".3gp")){
13Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
14Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)MovieInfomi=newMovieInfo();
15Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)mi.displayName=file.getName();
16Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)mi.path=file.getAbsolutePath();
17Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)list.add(mi);
18Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)returntrue;
19Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}

20Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}
elseif(file.isDirectory()){
21Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)getVideoFile(list,file);
22Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}

23Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)returnfalse;
24Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}

25Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}
);
26Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)}

当然,随着Android平台下的硬件设备越来越多,越来越强大。我们有理由相信,它以后将不仅仅只支持MP4和3GP格式的视频文件,所以我们必须使用两种方式结合的方法来获得最大的视频集合作为我们的视频列表。

四、播放过程中进度条progress的设定:

视频开始播放了,那么一个小麻烦出现了:什么时候设定进度条才更有效率?我这里有一种方法供大家参考,那就是通过Handler自己给自己发消息来达到不断设置进度条的目的。

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)HandlermyHandler=newHandler(){
2Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
3Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)@Override
4Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)publicvoidhandleMessage(Messagemsg){
5Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)//TODOAuto-generatedmethodstub
6Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
7Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)switch(msg.what){
8Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
9Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)casePROGRESS_CHANGED:
10Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
11Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)inti=vv.getCurrentPosition();
12Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)seekBar.setProgress(i);
13Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
14Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)i/=1000;
15Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)intminute=i/60;
16Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)inthour=minute/60;
17Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)intsecond=i%60;
18Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)minute%=60;
19Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)playedTextView.setText(String.format("%02d:%02d:%02d",hour,minute,second));
20Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
21Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)sendEmptyMessage(PROGRESS_CHANGED);
22Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)break;
23Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
24Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
25Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
26Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

当然,这种方法,需要首先发送一个初始消息来启动。

五、全屏与非全屏:

大家都知道,一般一个Activity设置全屏的方法有两种,一是在OnCreate中:

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)@Override
2Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)publicvoidonCreate(Bundleicicle){
3Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)super.onCreate(icicle);
4Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
5Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)requestWindowFeature(Window.FEATURE_NO_TITLE);
6Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Windowwin=getWindow();
7Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)win.setFlags(WindowManager.LayoutParams.NO_STATUS_BAR_FLAG,
8Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)WindowManager.LayoutParams.NO_STATUS_BAR_FLAG);
9Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
10Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)setContentView(R.layout.mylayout);
11Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
12Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)
13Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)

二是在AndroidManifest.xml中:

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)<activityandroid:name=".MyActivity"
2Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)android:label=""
3Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)android:theme="@android:style/Theme.NoTitleBar.Fullscreen">

然而,这两种方法都不能达到我们在视频播放过程中设置全屏与否的目的。因为它们都只能在初始化的时候决定全屏与否。那么我现在要说的就是第三种方法:

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

这种方法就可以在Activity运行过程中,动态地改变全屏与否。

六、音量调节:

音量调节的方法其实很简单,不过有人问到,我就在这里顺便说下:

1Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)AudioManageram=(AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
2Android平台下SeeJoPlayer视频播放器(功能介绍及源码解析,附源码)setIndex(am.getStreamVolume(AudioManager.STREAM_MUSIC));

好了,就写这些了吧。可能这些知识有人知道,或者还有些盲点我没有讲到。欢迎大家与我联系,大家一起多多讨论交流,并且整个源码都开放出来了,大家一定可以把来龙去脉弄得一清二楚的!最后,多谢大家听我罗嗦,欢迎使用SeeJoPlayer,欢迎阅读其源码!本文也欢迎大家转载,不过转载请注明出处:http://www.blogjava.net/zh-weir/archive/2010/01/24/310617.html

相关文章:

  • 2021-10-11
  • 2021-12-26
  • 2022-12-23
  • 2021-12-31
  • 2021-12-18
  • 2021-12-18
  • 2022-12-23
  • 2021-12-31
猜你喜欢
  • 2022-01-22
  • 2021-06-11
  • 2021-12-18
  • 2022-12-23
  • 2021-12-09
  • 2022-02-10
  • 2022-12-23
相关资源
相似解决方案