Android TTS实现简单阅读器

简单的Txt文本阅读器,主要用于介绍Google Android的TTS接口。

一、 TTS

在package android.speech.tts内,主要阅读下TextToSpeech.OnInitListener、TextToSpeech. OnUtteranceCompletedListener两个接口和TextToSpeech、TextToSpeech.Engine两个类。

具体还是自己去看下SDK文档吧。(我也是完整阅读过了的^^) 二、 TTS 引擎 以前在网上的例子,或者就我《 Android基础样例》里的中文TTS例子,都是eSpeak引擎实现的。这种方式是要用其封装的TTS接口,再通过下载TTS数据使用。 而Android的SDK中还提供了TTS服务的接口,用于供应商提供服务的。也就是语音合成服务商只管提供它的服务,开发者只管使用Android的TTS接口,用户自己安装想要的服务自己进行选择。

总之呢,我用的是讯飞语音TTS v1.0。有两个文件,一个是Service程序,一个是语音数据。下载网址:http://soft.shouji.com.cn/down/22160.html

1 )关于讯飞(貌似广告?) 好吧,少说点了,它也提供了个开发者平台。如下: 讯飞语音云: http://dev.voicecloud.cn/download.php?vt=1 有试了下它那语音分析,话说,弹出的框框能不能好看点啊。(做个小话筒就好了么T^T) 恩,还有,现在讯飞是要开始宣传了么?貌似3月22日什么开发者大会-_-!(又广告了?) 2 )其他中文引擎 参见文章:Android中文语音合成(TTS)各家引擎对比。(原网址打不开==,另外的网址就不贴了,搜下吧) 三、阅读器工程 现在学乖了,直接贴些代码得了==。代码中注释应该满清晰详细了^^。 1 )界面布局

布局由main.xml includes header.xml & footer.xml组成,并写有了定时收起等。

TtsFatherActivity.java
        
  1. /**
  2. *1)本类用于main.xml的布局控制。子类再去实现各控件的TTS相关功能。
  3. *2)用继承方式实现是为了利用布局中控件的onClick属性(懒得多写代码==!)。
  4. */
  5. publicabstractclassTtsFatherActivityextendsActivity{
  6. privateGestureDetectorgd;//手势检测
  7. privateGlobalUtilglobalUtil;//全局公用类
  8. privateScrollViewscrollView;//滚动视图
  9. privateLinearLayoutheaderLayout,footerLayout;//顶部、底部布局
  10. privateTextViewtextView;//文本标签
  11. privatestaticfinallongANIM_DURATION=500;//动画时间(毫秒)
  12. privatestaticfinalintDIALOG_TEXT_LIST=0;//文本列表对话框id
  13. privatefinalString[]textPaths=newString[]{"one.txt","two.txt",
  14. "浏览..."};//assets内文本资源路径
  15. protectedStringtextTitle;//文本标题
  16. protectedStringtextContent;//文本内容
  17. privateTimertimer;//计时器
  18. privatestaticfinallongTIMEOUT=2000;//超时时间
  19. privatestaticfinalintTIMER_LAYOUT_OUT=1;//布局收起
  20. privatebooleanisLayoutOut=false;//布局收起状态
  21. /**Handler处理操作*/
  22. publicHandlermHandler=newHandler(){
  23. @Override
  24. publicvoidhandleMessage(Messagemsg){
  25. switch(msg.what){
  26. caseTIMER_LAYOUT_OUT:
  27. /*headerLayout收起动画*/
  28. globalUtil.startTransAnim(headerLayout,
  29. GlobalUtil.AnimMode.UP_OUT,ANIM_DURATION);
  30. headerLayout.setVisibility(View.GONE);
  31. /*footerLayout收起动画*/
  32. globalUtil.startTransAnim(footerLayout,
  33. GlobalUtil.AnimMode.DOWN_OUT,ANIM_DURATION);
  34. footerLayout.setVisibility(View.GONE);
  35. isLayoutOut=true;//重置布局收起状态
  36. break;
  37. }
  38. }
  39. };
  40. @Override
  41. publicvoidonCreate(BundlesavedInstanceState){
  42. super.onCreate(savedInstanceState);
  43. setContentView(R.layout.main);
  44. gd=newGestureDetector(newMySimpleGesture());//手势检测处理
  45. globalUtil=GlobalUtil.getInstance();//获取全局公用类
  46. scrollView=(ScrollView)findViewById(R.id.scrollView);//获取滚动视图
  47. headerLayout=(LinearLayout)findViewById(R.id.headerLayout);//获取顶部布局
  48. footerLayout=(LinearLayout)findViewById(R.id.footerLayout);//获取底部布局
  49. textView=(TextView)findViewById(R.id.textView);
  50. setText(0);//默认显示“上邪.txt”
  51. newTimerLayoutOut();//定时收起布局
  52. }
  53. /**使用GestureDetector检测手势(ScrollView内也需监听时的方式)*/
  54. @Override
  55. publicbooleandispatchTouchEvent(MotionEventev){
  56. gd.onTouchEvent(ev);
  57. scrollView.onTouchEvent(ev);
  58. returnsuper.dispatchTouchEvent(ev);
  59. }
  60. /**onCreateDialog*/
  61. @Override
  62. protectedDialogonCreateDialog(intid){
  63. switch(id){
  64. caseDIALOG_TEXT_LIST:
  65. returnnewAlertDialog.Builder(this).setItems(textPaths,
  66. newDialogInterface.OnClickListener(){
  67. @Override
  68. publicvoidonClick(DialogInterfacedialog,intwhich){
  69. if(2==which){
  70. //跳转到文件浏览Activity
  71. startActivityForResult(newIntent(
  72. TtsFatherActivity.this,
  73. FileBrowserActivity.class),
  74. FileBrowserActivity.CODE_FILE_BROWSER);
  75. }else{
  76. setText(which);//设置文本内容
  77. }
  78. }
  79. }).create();
  80. }
  81. returnsuper.onCreateDialog(id);
  82. }
  83. @Override
  84. protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){
  85. if(requestCode==FileBrowserActivity.CODE_FILE_BROWSER){
  86. if(resultCode==RESULT_OK){
  87. //获得文件名称
  88. Stringfilename=data.getExtras().getString(
  89. FileBrowserActivity.KEY_FILENAME);
  90. this.textTitle=filename;
  91. try{
  92. //FileInputStreamfis=newFileInputStream(
  93. //newFile(filename));
  94. ////FileInputStream不支持mark/reset操作,不该直接这样
  95. //Stringencoding=globalUtil.getIsEncoding(fis);
  96. //textContent=globalUtil.is2Str(fis,encoding);
  97. /**
  98. *TXT简单判断编码类型后转字符串
  99. *
  100. *ps:
  101. *1)扯淡,3.58MB的txt读出来了==
  102. *看来需要转成BufferedReader以readLine()方式读好些啊
  103. *
  104. *2)TextView将大文本全部显示,这貌似...
  105. *
  106. *时间主要花费在文本显示过程,不改进了,暂时将就吧==
  107. *2.1)用View自定义个控件显示文本也蛮久的,未减少多少时间。
  108. *2.2)至于AsyncTask,文本显示还是要在UI线程的==。
  109. *
  110. *如果我们要仿个阅读器,用View自定义个控件还是必须的。
  111. *1)分段读取大文本,可以考虑3段(前后两段用于缓冲)
  112. *根据滑屏&显示内容等,注意文本显示衔接。
  113. *2)滚动条可以外面套个ScrollView。由各属性判断出大文本需要显示的高度,
  114. *重写onMeasure用setMeasuredDimension()设置好,才会有滚动条。
  115. *当然自己用scrollTo()、scrollBy()实现动画也是好的。
  116. *3)至于其他选中当前行啊什么的,慢慢写就成了...
  117. *
  118. *不知道大家还有什么好的想法没?
  119. */
  120. //longtime1=System.currentTimeMillis();
  121. textContent=globalUtil.is2Str(newFileInputStream(
  122. newFile(filename)));
  123. //longtime2=System.currentTimeMillis();
  124. //Log.e("TAG1","=="+(time2-time1)+"==");
  125. textView.setText(textContent);
  126. //longtime3=System.currentTimeMillis();
  127. //Log.e("TAG1","=="+(time3-time2)+"==");
  128. }catch(Exceptione){
  129. textView.setText(R.string.text_error);
  130. textContent="";
  131. }
  132. }
  133. }
  134. }
  135. /**设置文本内容*/
  136. privatevoidsetText(inttextIndex){
  137. this.textTitle=textPaths[textIndex];
  138. try{
  139. textContent=globalUtil.is2Str(getAssets().open(textTitle),
  140. "UTF-8");
  141. textView.setText(textContent);
  142. }catch(IOExceptione){
  143. textView.setText(R.string.text_error);
  144. textContent="";
  145. }
  146. }
  147. /**定时收起布局(已定时时重新开始定时)*/
  148. protectedvoidnewTimerLayoutOut(){
  149. if(null!=timer){
  150. timer.cancel();
  151. }
  152. timer=newTimer();
  153. //超时TIMEOUT退出
  154. timer.schedule(newTimerTask(){
  155. @Override
  156. publicvoidrun(){
  157. mHandler.sendEmptyMessage(TIMER_LAYOUT_OUT);
  158. }
  159. },TIMEOUT);
  160. }
  161. /**自定义手势类*/
  162. privateclassMySimpleGestureextendsSimpleOnGestureListener{
  163. /**双击第二下*/
  164. @Override
  165. publicbooleanonDoubleTap(MotionEvente){
  166. if(isLayoutOut){
  167. /*headerLayout进入动画*/
  168. headerLayout.setVisibility(View.VISIBLE);
  169. globalUtil.startTransAnim(headerLayout,
  170. GlobalUtil.AnimMode.UP_IN,ANIM_DURATION);
  171. /*footerLayout进入动画*/
  172. footerLayout.setVisibility(View.VISIBLE);
  173. globalUtil.startTransAnim(footerLayout,
  174. GlobalUtil.AnimMode.DOWN_IN,ANIM_DURATION);
  175. newTimerLayoutOut();//定时收起布局
  176. isLayoutOut=false;//重置布局收起状态
  177. }else{
  178. /*headerLayout退出动画*/
  179. globalUtil.startTransAnim(headerLayout,
  180. GlobalUtil.AnimMode.UP_OUT,ANIM_DURATION);
  181. headerLayout.setVisibility(View.GONE);
  182. /*footerLayout退出动画*/
  183. globalUtil.startTransAnim(footerLayout,
  184. GlobalUtil.AnimMode.DOWN_OUT,ANIM_DURATION);
  185. footerLayout.setVisibility(View.GONE);
  186. //取消定时收起动画
  187. if(null!=timer){
  188. timer.cancel();
  189. }
  190. isLayoutOut=true;//重置布局收起状态
  191. }
  192. returnfalse;
  193. }
  194. /**长按屏幕时*/
  195. @Override
  196. publicvoidonLongPress(MotionEvente){
  197. //显示文本列表对话框
  198. showDialog(DIALOG_TEXT_LIST);
  199. }
  200. }
  201. }
2 )TTS 控制 音量&语速控制也写了的^^。 TtsSampleActivity.java
        
  1. publicclassTtsSampleActivityextendsTtsFatherActivityimplements
  2. OnSeekBarChangeListener,TextToSpeech.OnInitListener,
  3. TextToSpeech.OnUtteranceCompletedListener{
  4. //privatestaticfinalStringTAG="TtsSampleActivity";//日志标记
  5. privateAudioManageraudioManager;//音频管理对象
  6. //TTS音量类型(AudioManager.STREAM_MUSIC=AudioManager.STREAM_TTS=11)
  7. privatestaticfinalintSTREAM_TTS=AudioManager.STREAM_MUSIC;
  8. privateTextToSpeechmTts;//TTS对象
  9. privatestaticfinalintREQ_CHECK_TTS_DATA=110;//TTS数据校验请求值
  10. privatebooleanisSetting=false;//进入设置标记
  11. privatebooleanisRateChanged=false;//速率改变标记
  12. privatebooleanisStopped=false;//TTS引擎停止发声标记
  13. privatefloatmSpeechRate=1.0f;//朗读速率
  14. privateSeekBarvolumeBar,speedBar;//音量&语速
  15. //合成声音资源文件的路径
  16. privatestaticfinalStringSAVE_DIR_PATH="/sdcard/AndroidTTS/";
  17. privatestaticfinalStringSAVE_FILE_PATH=SAVE_DIR_PATH+"sound.wav";
  18. @Override
  19. publicvoidonCreate(BundlesavedInstanceState){
  20. super.onCreate(savedInstanceState);
  21. //获得音频管理对象
  22. audioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);
  23. /*volumeBar*/
  24. volumeBar=(SeekBar)findViewById(R.id.volumeBar);
  25. volumeBar.setOnSeekBarChangeListener(this);
  26. //由当前音量设置进度(需保证进度上限=音频上限=15,否则按比例设置)
  27. volumeBar.setProgress(audioManager.getStreamVolume(STREAM_TTS));
  28. /*speedBar*/
  29. speedBar=(SeekBar)findViewById(R.id.speedBar);
  30. speedBar.setOnSeekBarChangeListener(this);
  31. initDirs(SAVE_DIR_PATH);//初始化文件夹路径
  32. }
  33. /**saveFileBtn点击事件*/
  34. publicvoidsaveFile(Viewv){
  35. //将文本合成声音资源文件
  36. intresId=TextToSpeech.SUCCESS==ttsSaveFile(textContent,
  37. SAVE_FILE_PATH)?R.string.synt_success:R.string.synt_fail;
  38. Toast.makeText(this,resId,Toast.LENGTH_SHORT).show();//Toast提示
  39. newTimerLayoutOut();//重新定时收起布局
  40. }
  41. /**playFileBtn点击事件*/
  42. publicvoidplayFile(Viewv){
  43. ttsPlayFile(SAVE_FILE_PATH);//播放指定的使用文件
  44. newTimerLayoutOut();//重新定时收起布局
  45. }
  46. /**stopBtn点击事件*/
  47. publicvoidstop(Viewv){
  48. ttsStop();//停止当前发声
  49. newTimerLayoutOut();//重新定时收起布局
  50. }
  51. /**playBtn点击事件*/
  52. publicvoidplay(Viewv){
  53. ttsPlay();//tts合成语音播放
  54. newTimerLayoutOut();//重新定时收起布局
  55. }
  56. /**settingBtn点击事件*/
  57. publicvoidsetting(Viewv){
  58. //跳转到“语音输入与输出”设置界面&设置标志位
  59. isSetting=toTtsSettings();
  60. newTimerLayoutOut();//重新定时收起布局
  61. }
  62. /**SeekBar进度改变时*/
  63. @Override
  64. publicvoidonProgressChanged(SeekBarseekBar,intprogress,
  65. booleanfromUser){
  66. switch(seekBar.getId()){
  67. caseR.id.volumeBar:
  68. //由设置当前TTS音量(需保证进度上限=音频上限=15,否则按比例设置)
  69. audioManager.setStreamVolume(STREAM_TTS,progress,0);
  70. break;
  71. caseR.id.speedBar:
  72. /*需要重新绑定TTS引擎,速度在onInit()里设置*/
  73. isRateChanged=true;//速率改变标记
  74. //最大值为20时,以下方式计算为0.5~2倍速
  75. mSpeechRate=(progress>=10)?(progress/10f)
  76. :(0.5f+progress/20f);
  77. //校验TTS引擎安装及资源状态,重新绑定引擎
  78. checkTtsData();
  79. break;
  80. }
  81. newTimerLayoutOut();//重新定时收起布局
  82. }
  83. /**SeekBar开始拖动时*/
  84. @Override
  85. publicvoidonStartTrackingTouch(SeekBarseekBar){
  86. }
  87. /**SeekBar结束拖动时*/
  88. @Override
  89. publicvoidonStopTrackingTouch(SeekBarseekBar){
  90. }
  91. /**
  92. *TTS引擎初始化时回调方法
  93. *
  94. *引擎相关参数(音量、语速)等都需在这设置。
  95. *1)创建完成后再去设置,会有意外的效果^^
  96. *2)音量也可由AudioManager进行控制(和音乐一个媒体流类型)
  97. */
  98. @Override
  99. publicvoidonInit(intstatus){
  100. if(status==TextToSpeech.SUCCESS){
  101. mTts.setSpeechRate(mSpeechRate);//设置朗读速率
  102. //设置发声合成监听,注意也需要在onInit()中做才有效
  103. mTts.setOnUtteranceCompletedListener(this);
  104. if(isRateChanged){
  105. ttsPlay();//tts合成语音播放
  106. isRateChanged=false;//重置标记位
  107. }
  108. }
  109. }
  110. /**
  111. *TTS引擎完成发声完成时回调方法
  112. *
  113. *1)stop()取消时也会回调
  114. *2)需在onInit()内设置接口
  115. *3)utteranceId由speak()时的请求参数设定
  116. *参数key:TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID
  117. */
  118. @Override
  119. publicvoidonUtteranceCompleted(finalStringutteranceId){
  120. /*测试该接口的Toast提示*/
  121. runOnUiThread(newRunnable(){
  122. @Override
  123. publicvoidrun(){
  124. intresId=isStopped?R.string.utte_stopped
  125. :R.string.utte_completed;
  126. //提示文本发生完成
  127. Toast.makeText(getApplicationContext(),
  128. getString(resId,utteranceId),Toast.LENGTH_SHORT)
  129. .show();
  130. }
  131. });
  132. }
  133. /**onActivityResult*/
  134. @Override
  135. protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){
  136. if(requestCode==REQ_CHECK_TTS_DATA){
  137. switch(resultCode){
  138. caseTextToSpeech.Engine.CHECK_VOICE_DATA_PASS://TTS引擎可用
  139. //针对于重新绑定引擎,需要先shutdown()
  140. if(null!=mTts){
  141. ttsStop();//停止当前发声
  142. ttsShutDown();//释放资源
  143. }
  144. mTts=newTextToSpeech(this,this);//创建TextToSpeech对象
  145. break;
  146. caseTextToSpeech.Engine.CHECK_VOICE_DATA_BAD_DATA://数据错误
  147. caseTextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_DATA://缺失数据资源
  148. caseTextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_VOLUME://缺少数据存储量
  149. notifyReinstallDialog();//提示用户是否重装TTS引擎数据的对话框
  150. break;
  151. caseTextToSpeech.Engine.CHECK_VOICE_DATA_FAIL://检查失败
  152. default:
  153. break;
  154. }
  155. }
  156. super.onActivityResult(requestCode,resultCode,data);
  157. }
  158. /**校验TTS引擎安装及资源状态*/
  159. privatebooleancheckTtsData(){
  160. try{
  161. IntentcheckIntent=newIntent();
  162. checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
  163. startActivityForResult(checkIntent,REQ_CHECK_TTS_DATA);
  164. returntrue;
  165. }catch(ActivityNotFoundExceptione){
  166. returnfalse;
  167. }
  168. }
  169. /**提示用户是否重装TTS引擎数据的对话框*/
  170. privatevoidnotifyReinstallDialog(){
  171. newAlertDialog.Builder(this).setTitle("TTS引擎数据错误")
  172. .setMessage("是否尝试重装TTS引擎数据到设备上?")
  173. .setPositiveButton("是",newDialogInterface.OnClickListener(){
  174. @Override
  175. publicvoidonClick(DialogInterfacedialog,intwhich){
  176. //触发引擎在TTS引擎在设备上安装资源文件
  177. IntentdataIntent=newIntent();
  178. dataIntent
  179. .setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
  180. startActivity(dataIntent);
  181. }
  182. }).setNegativeButton("否",null).show();
  183. }
  184. /**跳转到“语音输入与输出”设置界面*/
  185. privatebooleantoTtsSettings(){
  186. try{
  187. startActivity(newIntent("com.android.settings.TTS_SETTINGS"));
  188. returntrue;
  189. }catch(ActivityNotFoundExceptione){
  190. returnfalse;
  191. }
  192. }
  193. @Override
  194. protectedvoidonStart(){
  195. checkTtsData();//校验TTS引擎安装及资源状态
  196. super.onStart();
  197. }
  198. @Override
  199. protectedvoidonResume(){
  200. /*从设置返回后重新绑定TTS,避免仍用旧引擎*/
  201. if(isSetting){
  202. checkTtsData();//校验TTS引擎安装及资源状态
  203. isSetting=false;
  204. }
  205. super.onResume();
  206. }
  207. @Override
  208. protectedvoidonStop(){
  209. /*HOME键*/
  210. ttsStop();//停止当前发声
  211. super.onStop();
  212. }
  213. @Override
  214. publicvoidonBackPressed(){
  215. /*BACK键*/
  216. ttsStop();//停止当前发声
  217. ttsShutDown();//释放资源
  218. super.onBackPressed();
  219. }
  220. /**tts合成语音播放*/
  221. privateintttsPlay(){
  222. if(null!=mTts){
  223. isStopped=false;//设置标记
  224. /**
  225. *叙述text。
  226. *
  227. *1)参数2(intqueueMode)
  228. *1.1)QUEUE_ADD:增加模式。增加在队列尾,继续原来的说话。
  229. *1.2)QUEUE_FLUSH:刷新模式。中断正在进行的说话,说新的内容。
  230. *2)参数3(HashMap<String,String>params)
  231. *2.1)请求的参数,可以为null。
  232. *2.2)注意KEY_PARAM_UTTERANCE_ID。
  233. */
  234. HashMap<String,String>params=newHashMap<String,String>();
  235. params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,textTitle);
  236. returnmTts.speak(textContent,TextToSpeech.QUEUE_FLUSH,params);
  237. }
  238. returnTextToSpeech.ERROR;
  239. }
  240. ///**判断TTS是否正在发声*/
  241. //privatebooleanisSpeaking(){
  242. ////使用mTts.isSpeaking()判断时,第一次speak()返回true,多次就返回false了。
  243. //returnaudioManager.isMusicActive();
  244. //}
  245. /**停止当前发声,同时放弃所有在等待队列的发声*/
  246. privateintttsStop(){
  247. isStopped=true;//设置标记
  248. return(null==mTts)?TextToSpeech.ERROR:mTts.stop();
  249. }
  250. /**释放资源(解除语音服务绑定)*/
  251. privatevoidttsShutDown(){
  252. if(null!=mTts){
  253. mTts.shutdown();
  254. }
  255. }
  256. /**初始化文件夹路径*/
  257. privatevoidinitDirs(finalStringdirpath){
  258. Filefile=newFile(dirpath);
  259. if(!file.exists()){
  260. file.mkdirs();
  261. }
  262. }
  263. /**将文本合成声音资源文件*/
  264. privateintttsSaveFile(Stringtext,finalStringfilename){
  265. return(null==mTts)?TextToSpeech.ERROR:mTts.synthesizeToFile(
  266. text,null,filename);
  267. }
  268. /**播放指定的使用文件*/
  269. privateintttsPlayFile(finalStringfilename){
  270. //如果存在FILENAME_SAVE文件的话
  271. if(newFile(filename).exists()){
  272. try{
  273. /*使用MediaPlayer进行播放(没进行控制==)*/
  274. MediaPlayerplayer=newMediaPlayer();
  275. player.setDataSource(filename);
  276. player.prepare();
  277. player.start();
  278. returnTextToSpeech.SUCCESS;
  279. }catch(Exceptione){
  280. e.printStackTrace();
  281. returnTextToSpeech.ERROR;
  282. }
  283. }
  284. returnTextToSpeech.ERROR;
  285. }
  286. }
超了==,代码贴多了?下半部分,请至《 Android TTS实现简单阅读器(二)》。(工程附件也在后一部分中^^)

更多相关文章

  1. Android(安卓)软键盘顶起布局相关
  2. 《Android(安卓)UI基础教程》之读书笔记
  3. [android]如何使LinearLayout布局从右向左水平排列,而不是从左向
  4. 【android】小知识点整理qwq(三)
  5. android夜间模式的实现
  6. Android(安卓)Material Design(一)史上最全的材料设计控件大全
  7. 解决webview中输入框完成输入隐藏键盘后滑动界面又弹出软键盘问
  8. Android显示一个文本框的内容
  9. Android(安卓)M新控件知识整理

随机推荐

  1. Android(安卓)https ssl证书配置(使用okht
  2. Android(安卓)OpenGL ES学习笔记之绘制点
  3. 我把阿里、腾讯、字节跳动、美团等Androi
  4. Android(安卓)实现用户列表信息的功能,然
  5. 物联网温湿度显示控制项目(网页、Android
  6. Android--多线程之Handler
  7. 【Android】Android中使用JNI调用底层C++
  8. 手把手教你学Android(基础篇)
  9. (Android(安卓)studio)关于drawable文件夹
  10. Android浏览器打开本地app前端同学的两种