本文主要介绍如何构建新浪微博客户端。以网上流传weiboSina源码为例介绍,其下载地址为: http://download.csdn.net/detail/ryzhanglu/3453875。

1、项目概况

该项目文件列表如下:

其AndroidManifest.xml文件内容为:

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="cn.com.hh.view"
  4. android:versionCode="1"
  5. android:versionName="1.0">
  6. <application android:icon="@drawable/icon" android:label="@string/app_name"
  7. android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
  8. <!--   主Activity    -->
  9. <activity android:name="cn.com.hh.view.MyBlogActivity"
  10. android:label="@string/app_name">
  11. <intent-filter>
  12. <action android:name="android.intent.action.MAIN" />
  13. <category android:name="android.intent.category.LAUNCHER" />
  14. </intent-filter>
  15. </activity>
  16. <!--  注册 授权登录Activity    -->
  17. <activity android:name="cn.com.hh.view.AuthorizeActivity" android:launchMode="singleTask">
  18. <intent-filter>
  19. <action android:name="android.intent.action.VIEW" />
  20. <category android:name="android.intent.category.DEFAULT" />
  21. <category android:name="android.intent.category.BROWSABLE" />
  22. <data android:scheme="myapp" android:host="AuthorizeActivity" />
  23. </intent-filter>
  24. </activity>
  25. <!--  注册登录Activity     -->
  26. <activity android:name="cn.com.hh.view.LoginActivity" >
  27. <!--         <intent-filter>-->
  28. <!--             <action android:name="android.intent.action.VIEW" />-->
  29. <!--             <category android:name="android.intent.category.DEFAULT" />-->
  30. <!--             <category android:name="android.intent.category.BROWSABLE" />-->
  31. <!--             <data android:scheme="myapp" android:host="AuthorizeActivity" />-->
  32. <!--         </intent-filter>-->
  33. </activity>
  34. <!--  注册登录Activity     -->
  35. <activity android:name="cn.com.hh.view.HomeActivity" >
  36. </activity>
  37. <!--  注册登录Activity     -->
  38. <activity android:name="cn.com.hh.view.ViewActivity" >
  39. </activity>
  40. </application>
  41. <uses-permission android:name="android.permission.INTERNET" />
  42. </manifest>

2、oauth2认证

说说关于OAuth授权认证的事情,新浪开放api都必须在这个基础上才能调用,所以有必要专门来讲讲,前面的文章中已经提到过关于新浪微博提供了OAuth和Base OAuth两种认证方式,并且本项目采用OAuth认证方式,至于为什么采用这个OAuth认证而不采用Base OAuth认证原因很简单,自从Twitter只支持OAuth认证方式以来,各大应用都纷纷转向OAuth认证方式,而新浪微博的开放平台也将在近日停止Base OAuth的认证方式。
     OAuth的基本概念,OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。同样新浪微博提供OAuth认证也是为了保证用户账号和密码的安全,在这里通过OAuth建立普通新浪微博用户、客户端程序(我们正在开发的这个Android客户端程序)、新浪微博三者之间的相互信任关系,让客户端程序(我们正在开发的这个android客户端程序)不需要知道用户的账号和密码也能浏览、发布微博,这样有效的保护了用户账号的安全性不需要把账号密码透露给客户端程序又达到了通过客户端程序写微博看微博目的。这个是OAuth的作用。
      结合新浪微博的OAuth认证来说说具体的功能实现,首先罗列一下关键字组,下面四组关键字跟我们接下来OAuth认证有非常大的关系。
      第一组:(App Key和App Secret),这组参数就是本系列文本第一篇提到的建一个新的应用获取App Key和App Secret。
      第二组:(Request Token和Request Secret)
      第三组:(oauth_verifier)
      第四组:(user_id、Access Token和Access Secret) 
     新浪微博的OAuth认证过程,当用户第一次使用本客户端软件时,客户端程序用第一组作为参数向新浪微博发起请求,然后新浪微博经过验证后返回第二组参数给客户端软件同时表示新浪微博信任本客户端软件,当客户端软件获取第二组参数时作为参数引导用户浏览器跳至新浪微博的授权页面,然后用户在新浪的这个授权页面里输入自己的微博账号和密码进行授权,完成授权后根据客户端设定的回调地址把第三组参数返回给客户端软件并表示用户也信任本客户端软件,接下客户端软件把第二组参数和第三组参数作为参数再次向新浪微博发起请求,然后新浪微博返回第四组参数给客户端软件,第四组参数需要好好的保存起来这个就是用来代替用户的新浪账号和密码用的,在后面调用api时都需要。从这个过程来看用户只是在新浪微博的认证网页输入过账户和密码并没有在客户端软件里输入过账户和密码,客户端软件只保存了第四组数据并没有保存用户的账户和密码,这样有效的避免了账户和密码透露给新浪微博之外的第三方应用程序,保证 了安全性。
      本项目用为了方便开发采用了oauth-signpost开源项目进行OAuth认证开发,新建OAuth.java类文件对OA进行简单的封装,OAuth类主要有RequestAccessToken、GetAccessToken、SignRequest三个方法,第一个方法RequestAccessToken就是上面过程中用来获取第三组参数用的,GetAccessToken方法是用来获取第四组参数用,SignRequest方法是用来调用api用。由于采用了oauth-signpost开源项目简单了很多。具体代码如下:

[java] view plaincopy
  1. public class OAuth {
  2. private CommonsHttpOAuthConsumer httpOauthConsumer;
  3. private OAuthProvider httpOauthprovider;
  4. public String consumerKey;
  5. public String consumerSecret;
  6. public OAuth()
  7. {
  8. // 第一组:(App Key和App Secret)
  9. // 这组参数就是本系列文本第一篇提到的建一个新的应用获取App Key和App Secret。
  10. this("3315495489","e2731e7grf592c0fd7fea32406f86e1b");
  11. }
  12. public OAuth(String consumerKey,String consumerSecret)
  13. {
  14. this.consumerKey=consumerKey;
  15. this.consumerSecret=consumerSecret;
  16. }
  17. public Boolean RequestAccessToken(Activity activity,String callBackUrl){
  18. Boolean ret=false;
  19. try{
  20. httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey,consumerSecret);
  21. httpOauthprovider = new DefaultOAuthProvider("http://api.t.sina.com.cn/oauth/request_token","http://api.t.sina.com.cn/oauth/access_token","http://api.t.sina.com.cn/oauth/authorize");
  22. String authUrl = httpOauthprovider.retrieveRequestToken(httpOauthConsumer, callBackUrl);
  23. activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));
  24. ret=true;
  25. }catch(Exception e){
  26. }
  27. return ret;
  28. }
  29. public UserInfo GetAccessToken(Intent intent){
  30. UserInfo user=null;
  31. Uri uri = intent.getData();
  32. String verifier = uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER);
  33. try {
  34. httpOauthprovider.setOAuth10a(true);
  35. httpOauthprovider.retrieveAccessToken(httpOauthConsumer,verifier);
  36. } catch (OAuthMessageSignerException ex) {
  37. ex.printStackTrace();
  38. } catch (OAuthNotAuthorizedException ex) {
  39. ex.printStackTrace();
  40. } catch (OAuthExpectationFailedException ex) {
  41. ex.printStackTrace();
  42. } catch (OAuthCommunicationException ex) {
  43. ex.printStackTrace();
  44. }
  45. SortedSet<String> user_id= httpOauthprovider.getResponseParameters().get("user_id");
  46. String userId=user_id.first();
  47. String userKey = httpOauthConsumer.getToken();
  48. String userSecret = httpOauthConsumer.getTokenSecret();
  49. user=new UserInfo();
  50. user.setUserId(userId);
  51. user.setToken(userKey);
  52. user.setTokenSecret(userSecret);
  53. return user;
  54. }
  55. public HttpResponse SignRequest(String token,String tokenSecret,String url,List params)
  56. {
  57. HttpPost post = new HttpPost(url);
  58. //HttpClient httpClient = null;
  59. try{
  60. post.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8));
  61. } catch (UnsupportedEncodingException e) {
  62. e.printStackTrace();
  63. }
  64. //关闭Expect:100-Continue握手
  65. //100-Continue握手需谨慎使用,因为遇到不支持HTTP/1.1协议的服务器或者代理时会引起问题
  66. post.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
  67. return SignRequest(token,tokenSecret,post);
  68. }
  69. public HttpResponse SignRequest(String token,String tokenSecret,HttpPost post){
  70. httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey,consumerSecret);
  71. httpOauthConsumer.setTokenWithSecret(token,tokenSecret);
  72. HttpResponse response = null;
  73. try {
  74. httpOauthConsumer.sign(post);
  75. } catch (OAuthMessageSignerException e) {
  76. e.printStackTrace();
  77. } catch (OAuthExpectationFailedException e) {
  78. e.printStackTrace();
  79. } catch (OAuthCommunicationException e) {
  80. e.printStackTrace();
  81. }
  82. //取得HTTP response
  83. try {
  84. response = new DefaultHttpClient().execute(post);
  85. } catch (ClientProtocolException e) {
  86. e.printStackTrace();
  87. } catch (IOException e) {
  88. e.printStackTrace();
  89. }
  90. return response;
  91. }
  92. }

2、认证界面

根据上文的介绍,我们知道,需要获得用户的授权才可以获得对该用户新浪微博的操作权限,在项目中对此是这样处理的。其界面如下:

用户进入应用以后,会转到是否对新浪微博进行授权界面,点击“开始”后启动浏览器进入新浪微博授权界面,该界面是新浪制作的,与本应用无关。用户输入账号和密码以后,点击“授权”,进行授权操作。该界面源码如下:

[java] view plaincopy
  1. public class AuthorizeActivity extends Activity {
  2. private Dialog dialog;
  3. private OAuth auth;
  4. private static final String CallBackUrl = "myapp://AuthorizeActivity";
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.authorize);
  9. View diaView=View.inflate(this, R.layout.dialog, null);
  10. dialog = new Dialog(AuthorizeActivity.this,R.style.dialog);
  11. dialog.setContentView(diaView);
  12. dialog.show();
  13. ImageButton stratBtn=(ImageButton)diaView.findViewById(R.id.btn_start);
  14. stratBtn.setOnClickListener(new OnClickListener(){
  15. public void onClick(View arg0) {
  16. auth=new OAuth("30632531","f539cb169860ed99cf8c1861c5da34f6");
  17. auth.RequestAccessToken(AuthorizeActivity.this, CallBackUrl);
  18. }
  19. });
  20. }
  21. @Override
  22. protected void onNewIntent(Intent intent) {
  23. super.onNewIntent(intent);
  24. //在这里处理获取返回的oauth_verifier参数
  25. UserInfo user= auth.GetAccessToken(intent);
  26. if(user!=null){
  27. DataHelper helper=new DataHelper(this);
  28. String uid=user.getUserId();
  29. if(helper.HaveUserInfo(uid))
  30. {
  31. helper.UpdateUserInfo(user);
  32. Log.e("UserInfo", "update");
  33. }else
  34. {
  35. helper.SaveUserInfo(user);
  36. Log.e("UserInfo", "add");
  37. }
  38. }
  39. }
  40. }

从该源码中我们可以发现,在AuthorizeActivity界面,用户点击“授权”以后,又重新进入AuthorizeActivity界面(这一步与AndroidManifest.xml文件中AuthorizeActivity项的配置有关),此时会调用onNewIntent接口。

这里有个地方需要注意,授权成功后重新进入AuthorizeActivity界面,执行onNewIntent函数后并不会自动跳转到其他界面。这里需要添加新的跳转代码。

3、onNewIntent调用时机

这一部分介绍一下onNewIntent的调用时机。

在IntentActivity中重写下列方法:onCreate onStart onRestart  onResume  onPause onStop onDestroy  onNewIntent
3.1、其他应用发Intent,执行下列方法:
I/@@@philn(12410): onCreate
I/@@@philn(12410): onStart
I/@@@philn(12410): onResume
发Intent的方法:
Uri uri = Uri.parse("philn://blog.163.com");
Intent it = new Intent(Intent.ACTION_VIEW, uri);    
startActivity(it);
3.2、接收Intent声明:

[html] view plaincopy
  1. <activity android:name="cn.com.hh.view.AuthorizeActivity" android:launchMode="singleTask">
  2. <intent-filter>
  3. <action android:name="android.intent.action.VIEW" />
  4. <category android:name="android.intent.category.DEFAULT" />
  5. <category android:name="android.intent.category.BROWSABLE" />
  6. <data android:scheme="myapp" android:host="AuthorizeActivity" />
  7. </intent-filter>
  8. </activity>

3.3、如果IntentActivity处于任务栈的顶端(AndroidManifest.xml配置中的android:launchMode="singleTask" 项),也就是说之前打开过的Activity,现在处于
I/@@@philn(12410): onPause
I/@@@philn(12410): onStop 状态的话
其他应用再发送Intent的话,执行顺序为:
I/@@@philn(12410): onNewIntent
I/@@@philn(12410): onRestart
I/@@@philn(12410): onStart
I/@@@philn(12410): onResume
        在Android应用程序开发的时候,从一个Activity启动另一个Activity并传递一些数据到新的Activity上非常简单,但是当您需要让后台运行的Activity回到前台并传递一些数据可能就会存在一点点小问题。
        首先,在默认情况下,当您通过Intent启到一个Activity的时候,就算已经存在一个相同的正在运行的Activity,系统都会创建一个新的Activity实例并显示出来。为了不让Activity实例化多次,我们需要通过在AndroidManifest.xml配置activity的加载方式(launchMode)以实现单任务模式,如下所示:

[java] view plaincopy
  1. <activity android:label="@string/app_name" android:launchmode="singleTask"android:name="Activity1">
  2. </activity>

launchMode为singleTask的时候,通过Intent启到一个Activity,如果系统已经存在一个实例,系统就会将请求发送到这个实例上,但这个时候,系统就不会再调用通常情况下我们处理请求数据的onCreate方法,而是调用onNewIntent方法,如下所示:

[java] view plaincopy
  1. protected void onNewIntent(Intent intent) {
  2. super.onNewIntent(intent);
  3. setIntent(intent);//must store the new intent unless getIntent() will return the old one
  4. processExtraData();
  5. }

不要忘记,系统可能会随时杀掉后台运行的Activity,如果这一切发生,那么系统就会调用onCreate方法,而不调用onNewIntent方法,一个好的解决方法就是在onCreate和onNewIntent方法中调用同一个处理数据的方法,如下所示:

[java] view plaincopy
  1. public void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.main);
  4. processExtraData();
  5. }
  6. protected void onNewIntent(Intent intent) {
  7. super.onNewIntent(intent);
  8. setIntent(intent);//must store the new intent unless getIntent() will return the old one
  9. processExtraData()
  10. }
  11. private void processExtraData(){
  12. Intent intent = getIntent();
  13. //use the data received here
  14. }

4、获得微博数据

获得用户认证后,就可以获得第四组参数,就可以依次获得用户数据了。

在项目中这一步是在HomeActivity界面中完成的,其中获得微博数据部分函数的源码如下:

[java] view plaincopy
  1. private void loadList(){
  2. if(ConfigHelper.nowUser==null)
  3. {
  4. }
  5. else
  6. {
  7. user=ConfigHelper.nowUser;
  8. //显示当前用户名称
  9. TextView showName=(TextView)findViewById(R.id.showName);
  10. showName.setText(user.getUserName());
  11. OAuth auth=new OAuth();
  12. String url = "http://api.t.sina.com.cn/statuses/friends_timeline.json";
  13. //          String url = "http://api.t.sina.com.cn/statuses/public_timeline.json";
  14. List<BasicNameValuePair> params=new ArrayList<BasicNameValuePair>();
  15. params.add(new BasicNameValuePair("source", auth.consumerKey));
  16. HttpResponse response = auth.SignRequest(user.getToken(), user.getTokenSecret(), url, params);
  17. if (200 == response.getStatusLine().getStatusCode()){
  18. try {
  19. InputStream is = response.getEntity().getContent();
  20. Reader reader = new BufferedReader(new InputStreamReader(is), 4000);
  21. StringBuilder buffer = new StringBuilder((int) response.getEntity().getContentLength());
  22. try {
  23. char[] tmp = new char[1024];
  24. int l;
  25. while ((l = reader.read(tmp)) != -1) {
  26. buffer.append(tmp, 0, l);
  27. }
  28. } finally {
  29. reader.close();
  30. }
  31. String string = buffer.toString();
  32. //Log.e("json", "rs:" + string);
  33. ((org.apache.http.HttpResponse) response).getEntity().consumeContent();
  34. JSONArray data=new JSONArray(string);
  35. for(int i=0;i<data.length();i++)
  36. {
  37. JSONObject d=data.getJSONObject(i);
  38. //Log.e("json", "rs:" + d.getString("created_at"));
  39. if(d!=null){
  40. JSONObject u=d.getJSONObject("user");
  41. if(d.has("retweeted_status")){
  42. JSONObject r=d.getJSONObject("retweeted_status");
  43. }
  44. //微博id
  45. String id=d.getString("id");
  46. String userId=u.getString("id");
  47. String userName=u.getString("screen_name");
  48. String userIcon=u.getString("profile_image_url");
  49. //                          Log.e("userIcon", userIcon);
  50. String time=d.getString("created_at");
  51. String text=d.getString("text");
  52. Boolean haveImg=false;
  53. if(d.has("thumbnail_pic")){
  54. haveImg=true;
  55. //String thumbnail_pic=d.getString("thumbnail_pic");
  56. //Log.e("thumbnail_pic", thumbnail_pic);
  57. }
  58. Date startDate=new Date(time);
  59. Date nowDate = Calendar.getInstance().getTime();
  60. time=new DateUtilsDef().twoDateDistance(startDate,nowDate);
  61. if(wbList==null){
  62. wbList=new ArrayList<WeiBoInfo>();
  63. }
  64. WeiBoInfo w=new WeiBoInfo();
  65. w.setId(id);
  66. w.setUserId(userId);
  67. w.setUserName(userName);
  68. w.setTime(time +" 前");
  69. w.setText(text);
  70. w.setHaveImage(haveImg);
  71. w.setUserIcon(userIcon);
  72. wbList.add(w);
  73. }
  74. }
  75. }catch (IllegalStateException e) {
  76. e.printStackTrace();
  77. } catch (IOException e) {
  78. e.printStackTrace();
  79. } catch (JSONException e) {
  80. e.printStackTrace();
  81. }
  82. }
  83. if(wbList!=null)
  84. {
  85. WeiBoAdapater adapater = new WeiBoAdapater();
  86. ListView Msglist=(ListView)findViewById(R.id.Msglist);
  87. Msglist.setOnItemClickListener(new OnItemClickListener(){
  88. public void onItemClick(AdapterView<?> arg0, View view,int arg2, long arg3) {
  89. Object obj=view.getTag();
  90. if(obj!=null){
  91. String id=obj.toString();
  92. Intent intent = new Intent(HomeActivity.this,ViewActivity.class);
  93. Bundle b=new Bundle();
  94. b.putString("key", id);
  95. intent.putExtras(b);
  96. startActivity(intent);
  97. }
  98. }
  99. });
  100. Msglist.setAdapter(adapater);
  101. }
  102. }
  103. loadingLayout.setVisibility(View.GONE);
  104. }

其中根据新浪提供的API对用户数据进行操作。这里只是获得用户所发布的微博信息,其api为http://api.t.sina.com.cn/statuses/friends_timeline.json。这里同时需要提供第四组参数,params.add(new BasicNameValuePair("source", auth.consumerKey));。其返回的数据格式是json需要对其进行解析。对于json格式的解析可以参看本人博文《 android基础知识11:json解析及简单例子》。

参考文献:

http://www.cnblogs.com/hll2008/archive/2011/01/03/1923674.html

及其系列文章

android项目源码解析04:新浪微博客户端源码解析相关推荐

  1. 【Android项目】本地FM收音机开发及源码简析

    [Android项目]本地FM收音机开发及源码简析 目录 1.概述 2.收音机的基本原理 3.收音机其他信息 RDS功能 4.Android开发FM收音机源码解析 5.App层如何设计本地FM应用 6 ...

  2. Android应用源码仿暴风影音安卓客户端源码

    Android应用源码仿暴风影音安卓客户端源码 本项目是一个模仿暴风影音的UI项目源码,仿照的界面有菜单页,主页,分类页等,项目内的所有数据都使用的本地模拟数据,仿照度一般在大分辨设备上布局显示会有问 ...

  3. ubuntu镜像源更换_ubuntu16.04更换镜像源

    1.备份原有 cp /etc/apt/sources.list /etc/apt/sources.list.old 2.打开阿里巴巴镜像源:  https://opsx.alibaba.com/mir ...

  4. Android学习笔记---26_网络通信之资讯客户端,使用pull解析器,解析,从网络中获得的自定义xml文件

    7.25_网络通信之资讯客户端 ---------------------------- 1.网络中一般是使用自己定义的格式比如:   案例:酷6网的视频客户端有一个功能:"在手机上显示最新 ...

  5. ubuntu linux 163源,Ubuntu 16.04 163网易源设置(自动化配置脚本)

    1.简介 1.1 介绍 很多时候,为了方便 操作,条件允许的情况下,直接 联网 进行安装,但是 系统 所配置的 apt源 配置的 url 地址 并非 国内的,获取资源很慢,所以 需要 配置成 国内的 ...

  6. 新浪微博客户端源码 android

    最近做作业选了这个题目..其实难度还是挺大的..最近要考试,本来有很多心得要写,来不及,先附上部分亮点以及源码: 1.OAuth1.0认证 2.支持下拉刷新,自动加载更多的ListView--Auto ...

  7. Jenkins构建Android项目发送到蒲公英并生成二维码

    环境准备: 安装完Jenkins后 1.java环境 #根据开发所需版本安装 2.安装gradle 下载地址:https://downloads.gradle-dn.com/distributions ...

  8. android 快传 源码_app源码之-仿茄子快传源码,Android项目源码类似茄子快传的快传项目包括服务端...

    商品属性 品牌其他 语言PHP 数据库 移动端无 大小48 MB 规格无 授权无 源文件无 安装环境 安装服务 主机类型 伪静态 操作系统 安装方式 web服务 商品介绍 本项目是一个基于安卓的 ...

  9. android 快传 源码_安卓APP仿茄子快传源码,Android项目源码类似茄子快传的快传项目包括服务端...

    适用范围:安卓APP仿茄子快传源码,Android项目源码类似茄子快传的快传项目包括服务端 演示地址:(以截图为准) 运行环境:Android+PC+web 其他说明: 本项目是一个基于安卓的类似茄子 ...

最新文章

  1. 利用nginx的fastcgi_cache模块来做缓存
  2. jdk安装包_第一章(第1节):安装JDK
  3. python gevent asyncio_python用from gevent import monkey; monkey.patch_all()之后报ssl等错误
  4. realme GT2 Pro获3C认证:65W超级快充加持
  5. 2021年四川省副高考试成绩查询,2021年四川省教育考试院成绩查询登录入口
  6. Mac彻底卸载搜狗输入法
  7. 两组树形数据的比对_Python数据分析-可视化“大佬”之Seaborn
  8. STC12LE5620AD RAM问题
  9. 常用功能错误不是问题,对待行为让人绝望
  10. Hadoop单机配置
  11. 微信公众平台开发之微信红包的实现
  12. 上海mba学费一览表2021
  13. 【解决方案】Error response from daemon: Conflict. The container name /mongo is already in use by contain
  14. 学计算机画素描吗,用电脑制作一张素描画
  15. LT01 创建转储单
  16. 向中级程序员转变必备的10个秘诀
  17. LINGO 12.0安装步骤
  18. PPT结尾只会说“谢谢”?学会这些PPT结尾,观众主动为你鼓掌
  19. 关于旋转平移的理解与思考
  20. 2021最新的笔记本散热器排行榜,全新的笔记本散热器排行

热门文章

  1. 【代码】睡眠排序python实现
  2. 可视化HTA窗体设计器-HtaMaker 界面介绍及使用方法,下载 | HTA VBS可视化脚本编写
  3. android 老人模式吗,父母适合用安卓还是iPhone?看完这些适老化功能再做决定
  4. Gustafson定律
  5. PHP laravel系列之bootstrap美化
  6. 文献综述 笔记软件_论坛软件综述
  7. win7和win10系统的安装教程
  8. 计算机网络安全等级可以划分为几级,网络安全级别划分为几个等级?
  9. UUID-五个版本-v1|v2|v3|v4|v5-使用说明
  10. 浅谈曲面参数化---顾险峰(老顾谈几何)