开始接触学习android已经有3个礼拜了,一直都是对着android的sdk文档写Tutorials从Hello World到Notepad Tutorial算是初步入门了吧,刚好最近对微博感兴趣就打算开发个android版本的新浪微博客户端作为练手项目,并且以随笔的方式详细的记录开发的全过程。本人对java语言以及eclipse Ide都是初次应用基本上属于边学边用,做移动设备上的东西也是第一次,总的来说属于无基础、无经验、无天赋的纯三无人员,还请广大同学们多多给予指点。  开发第一件事情,那就是开发工具以及环境,我的配置是Eclipse Helios (3.6.1) + Adroid2.2,具体的环境搭建我就不罗嗦了,google一下一大堆,光博客园里都能搜到很多篇了。  开发第二件事情,既然是开发新浪的微博客户端,那就先去新浪申请微博账号然后登陆后到新浪的开放平台,新浪的开放平台提供的新浪微博对外的api接口,在我的应用中创建一个新的应用获取App Key和App Secret,这2个值后面会有用到先记录下来。在新浪的开放平台中提供了开发文档、SDK、接口测试工具等,本人决定直接通过新浪的Rest Api进行开发并不打算使用新浪提供的SDK,据说新浪提供的java版的SDK并不能直接用来进行android的开发需要进行一定的修改才能使用,只是听说我没有试过不一定准确。  最后在说一下,我准备分为UI和功能两部分分别进行说明讲解,据我自己的情况大部分的时间都花在的UI的设计和实现上了,编码倒反而工作量小多了,所以特别把UI部分分出来讲。  最后还要在说一下,很抱歉上面内容基本上属于废话没有什么实质内容了但是既然是第一篇还是得象征性的交代一下,从下篇开始讲具体的内容。本软件设定用户第一个接触到的功能就是页面载入等待功能,这个功能对使用者来说就是一个持续1、2秒钟的等待页面,在用户等待的同时程序做一些必要的检查以及数据准备工作,载入页面分为UI篇和功能篇,从表及里首先是UI的实现,一个软件除功能之外还得有一个光鲜的外表也是非常重要的,尽管本人设计水平一般但是还是亲自操刀用ps先做了一下设计效果图如下:  一、接下来的任务就是在android中实现这样的效果显示,从这个效果的设计分别把图片分成背景、版本号部分、软件名称和图标、作者名称和blog四个部分,按照这样的思路把分别生成4张png的图片,背景部分考虑实现横屏和竖屏切换额外添加一张横屏背景图,然后新建android工程,我这里的名称为MySinaWeibo,android版本勾选2.2,并且创建名为MainActivity的Activity作为整个软件的起始页面,然后把上面的这些图片保存到项目的res/drawable-mdpi文件夹下,关于res目录下的drawable-mdpi、drawable-ldpi,、drawable-hdpi三个文件夹的区别,mdpi 里面主要放中等分辨率的图片,如HVGA (320x480)。ldpi里面主要放低分辨率的图片,如QVGA (240x320)。hdpi里面主要放高分辨率的图片,如WVGA (480x800),FWVGA (480x854)。android系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片,在开发程序时为了兼容不同平台不同屏幕,建议各自文件夹根据需求均存放不同版本图片,我这里就不进行这么多的考虑了。  二、完成图片资源的准备后接下就是layout文件的编写, 在res/layout文件夹下新建main.xml文件,这个layout采用LinearLayout控件作为顶层控件,然后用ImageView控件分别实现版本号图片顶部靠左对齐显示、软件名称和图标图片居中对齐、作者名称和blog图片底部靠右对齐。注意在版本号图片显示ImageView控件下面添加一个RelativeLayout控件作为软件名称和图标图片ImageVIew和作者名称和blog图片ImageView的父控件用来控制居中对齐已经底部对齐的实现,具体代码如下:代码view plaincopy to clipboardprint? 三、在ec打开名为MainActivity的Activity源代码文件进行编辑,onCreate部分代码如下: view plaincopy to clipboardprint? public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } 然后运行项目可以在模拟器中显示,上面的几个图片都按照设计的位置和效果进行显示只是整个页面的背景还是黑色的,接下来就是背景部分的显示实现,由于为了实现横竖屏切换显示,背景图的显示采用代码进行控制显示,首先用如下方法获取当前手机是横屏还是竖屏: view plaincopy to clipboardprint? //获取屏幕方向 public static int ScreenOrient(Activity activity) { int orient = activity.getRequestedOrientation(); if(orient != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE && orient != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { //宽>高为横屏,反正为竖屏 WindowManager windowManager = activity.getWindowManager(); Display display = windowManager.getDefaultDisplay(); int screenWidth = display.getWidth(); int screenHeight = display.getHeight(); orient = screenWidth < screenHeight ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; } return orient; } 然后编写一个名为AutoBackground的公共方法用来实现屏幕背景的自动切换,后面的几乎每一个功能页面都需要用到这个方法 view plaincopy to clipboardprint? public static void AutoBackground(Activity activity,View view,int Background_v, int Background_h) { int orient=ScreenOrient(activity); if (orient == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { //纵向 view.setBackgroundResource(Background_v); }else{ //横向 view.setBackgroundResource(Background_h); } } 完成上述两方法后在 MainActivity的onCreate方法中调用AutoBackground方法进行屏幕自动切换: view plaincopy to clipboardprint? LinearLayout layout=(LinearLayout)findViewById(R.id.layout); //背景自动适应 AndroidHelper.AutoBackground(this, layout, R.drawable.bg_v, R.drawable.bg_h); 到此完成了载入页面的UI部分的实现,测试运行模拟器中查看效果,基本上跟最上面的设计效果图相符,测试效果图如下: 通过上一篇文章(android开发我的新浪微博客户端-载入页面UI篇(1.1))已经完成了载入页面的UI部分的实现,效果如上图,接下来在上面的基础上完成载入页面的功能代码。  首先说明一下新浪微博提供了OAuth和Base OAuth两种认证方式(如果不知道什么是OAuth和Base OAuth请自己google一下恶补,同时接下来的2篇随笔也会对这方面进行详细的说明以及具体实现),本项目是采用OAuth认证方式,采用这种方式就需要有用户的新浪UserID、Access Token、Access Secret这3样东西才能自由便利的调用新浪的开放接口,本项目是这样做的当用户第一次使用软件时进行授权认证获取这3样东西的时候存储到sqlite库中以便用户下次使用时不需要重新进行繁琐的授权认证操作直接从sqlite库中读取出来即可,由于这样的需求载入页面的功能设定是这样:当用户打开软件显示载入页面时开始检查sqlite库中是否已经保存有用户的新浪微博的UserID号、Access Token、Access Secret的记录,如果一条记录都没有那就说明用户是第一次使用本软件那么跳到认证授权页面进行授权认证操作(认证授权功能在接下来的两篇中进行实现讲解)获取这3个值保存到sqlite库中,如果已经包括了记录,那么读取这些记录的UserID号、Access Token、Access Secret值然后根据这3个值调用新浪的api接口获取这些记录对应的用户昵称和用户头像图标等信息。  上面功能设定中涉及到sqlite数据库的创建、数据表的创建、数据记录的添加、数据记录的读取等操作,这里新建名为SqliteHelper.java类文件提供sqlite数据表的创建、更新等,代码如下: view plaincopy to clipboardprint? public class SqliteHelper extends SQLiteOpenHelper{ //用来保存 UserID、Access Token、Access Secret 的表名 public static final String TB_NAME="users"; public SqliteHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } //创建表 @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS "+ TB_NAME+"("+ UserInfo.ID+" integer primary key,"+ UserInfo.USERID+" varchar,"+ UserInfo.TOKEN+" varchar,"+ UserInfo.TOKENSECRET+" varchar,"+ UserInfo.USERNAME+" varchar,"+ UserInfo.USERICON+" blob"+ ")" ); Log.e("Database","onCreate"); } //更新表 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TB_NAME); onCreate(db); Log.e("Database","onUpgrade"); } //更新列 public void updateColumn(SQLiteDatabase db, String oldColumn, String newColumn, String typeColumn){ try{ db.execSQL("ALTER TABLE " + TB_NAME + " CHANGE " + oldColumn + " "+ newColumn + " " + typeColumn ); }catch(Exception e){ e.printStackTrace(); } } } 接下来新建名为DataHelper.java类文件实现用户记录的创建、更新、删除等,代码如下 view plaincopy to clipboardprint? public class DataHelper { //数据库名称 private static String DB_NAME = "mysinaweibo.db"; //数据库版本 private static int DB_VERSION = 2; private SQLiteDatabase db; private SqliteHelper dbHelper; public DataHelper(Context context){ dbHelper=new SqliteHelper(context,DB_NAME, null, DB_VERSION); db= dbHelper.getWritableDatabase(); } public void Close() { db.close(); dbHelper.close(); } //获取users表中的UserID、Access Token、Access Secret的记录 public List GetUserList(Boolean isSimple) { List userList = new ArrayList(); Cursor cursor=db.query(SqliteHelper.TB_NAME, null, null, null, null, null, UserInfo.ID+" DESC"); cursor.moveToFirst(); while(!cursor.isAfterLast()&& (cursor.getString(1)!=null)){ UserInfo user=new UserInfo(); user.setId(cursor.getString(0)); user.setUserId(cursor.getString(1)); user.setToken(cursor.getString(2)); user.setTokenSecret(cursor.getString(3)); if(!isSimple){ user.setUserName(cursor.getString(4)); ByteArrayInputStream stream = new ByteArrayInputStream(cursor.getBlob(5)); Drawable icon= Drawable.createFromStream(stream, "image"); user.setUserIcon(icon); } userList.add(user); cursor.moveToNext(); } cursor.close(); return userList; } //判断users表中的是否包含某个UserID的记录 public Boolean HaveUserInfo(String UserId) { Boolean b=false; Cursor cursor=db.query(SqliteHelper.TB_NAME, null, UserInfo.USERID + "=" + UserId, null, null, null,null); b=cursor.moveToFirst(); Log.e("HaveUserInfo",b.toString()); cursor.close(); return b; } //更新users表的记录,根据UserId更新用户昵称和用户图标 public int UpdateUserInfo(String userName,Bitmap userIcon,String UserId) { ContentValues values = new ContentValues(); values.put(UserInfo.USERNAME, userName); // BLOB类型 final ByteArrayOutputStream os = new ByteArrayOutputStream(); // 将Bitmap压缩成PNG编码,质量为100%存储 userIcon.compress(Bitmap.CompressFormat.PNG, 100, os); // 构造SQLite的Content对象,这里也可以使用raw values.put(UserInfo.USERICON, os.toByteArray()); int id= db.update(SqliteHelper.TB_NAME, values, UserInfo.USERID + "=" + UserId, null); Log.e("UpdateUserInfo2",id+""); return id; } //更新users表的记录 public int UpdateUserInfo(UserInfo user) { ContentValues values = new ContentValues(); values.put(UserInfo.USERID, user.getUserId()); values.put(UserInfo.TOKEN, user.getToken()); values.put(UserInfo.TOKENSECRET, user.getTokenSecret()); int id= db.update(SqliteHelper.TB_NAME, values, UserInfo.USERID + "=" + user.getUserId(), null); Log.e("UpdateUserInfo",id+""); return id; } //添加users表的记录 public Long SaveUserInfo(UserInfo user) { ContentValues values = new ContentValues(); values.put(UserInfo.USERID, user.getUserId()); values.put(UserInfo.TOKEN, user.getToken()); values.put(UserInfo.TOKENSECRET, user.getTokenSecret()); Long uid = db.insert(SqliteHelper.TB_NAME, UserInfo.ID, values); Log.e("SaveUserInfo",uid+""); return uid; } //删除users表的记录 public int DelUserInfo(String UserId){ int id= db.delete(SqliteHelper.TB_NAME, UserInfo.USERID +"="+UserId, null); Log.e("DelUserInfo",id+""); return id; } } 完成上面的代码后,我们需要在载入页面中调用上面的方法实现sqlite库中是否已经保存有用户的新浪微博的UserID号、Access Token、Access Secret的记录的功能在MainActivity的onCreate方法添加代码: view plaincopy to clipboardprint? public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ...... //获取账号列表 dbHelper=new DataHelper(this); List userList= dbHelper.GetUserList(true); if(userList.isEmpty())//如果为空说明第一次使用跳到AuthorizeActivity页面进行OAuth认证 { Intent intent = new Intent(); intent.setClass(MainActivity.this, AuthorizeActivity.class); startActivity(intent); } else//如果不为空读取这些记录的UserID号、Access Token、Access Secret值 //然后根据这3个值调用新浪的api接口获取这些记录对应的用户昵称和用户头像图标等信息。 { for(UserInfo user:userList){ ...... } } } 本篇说说关于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开源项目简单了很多。具体代码如下: view plaincopy to clipboardprint? public class OAuth { private CommonsHttpOAuthConsumer httpOauthConsumer; private OAuthProvider httpOauthprovider; public String consumerKey; public String consumerSecret; public OAuth() { // 第一组:(App Key和App Secret) // 这组参数就是本系列文本第一篇提到的建一个新的应用获取App Key和App Secret。 this("3315495489","e2731e7grf592c0fd7fea32406f86e1b"); } public OAuth(String consumerKey,String consumerSecret) { this.consumerKey=consumerKey; this.consumerSecret=consumerSecret; } public Boolean RequestAccessToken(Activity activity,String callBackUrl){ Boolean ret=false; try{ httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey,consumerSecret); 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"); String authUrl = httpOauthprovider.retrieveRequestToken(httpOauthConsumer, callBackUrl); activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl))); ret=true; }catch(Exception e){ } return ret; } public UserInfo GetAccessToken(Intent intent){ UserInfo user=null; Uri uri = intent.getData(); String verifier = uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER); try { httpOauthprovider.setOAuth10a(true); httpOauthprovider.retrieveAccessToken(httpOauthConsumer,verifier); } catch (OAuthMessageSignerException ex) { ex.printStackTrace(); } catch (OAuthNotAuthorizedException ex) { ex.printStackTrace(); } catch (OAuthExpectationFailedException ex) { ex.printStackTrace(); } catch (OAuthCommunicationException ex) { ex.printStackTrace(); } SortedSet user_id= httpOauthprovider.getResponseParameters().get("user_id"); String userId=user_id.first(); String userKey = httpOauthConsumer.getToken(); String userSecret = httpOauthConsumer.getTokenSecret(); user=new UserInfo(); user.setUserId(userId); user.setToken(userKey); user.setTokenSecret(userSecret); return user; } public HttpResponse SignRequest(String token,String tokenSecret,String url,List params) { HttpPost post = new HttpPost(url); //HttpClient httpClient = null; try{ post.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //关闭Expect:100-Continue握手 //100-Continue握手需谨慎使用,因为遇到不支持HTTP/1.1协议的服务器或者代理时会引起问题 post.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false); return SignRequest(token,tokenSecret,post); } public HttpResponse SignRequest(String token,String tokenSecret,HttpPost post){ httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey,consumerSecret); httpOauthConsumer.setTokenWithSecret(token,tokenSecret); HttpResponse response = null; try { httpOauthConsumer.sign(post); } catch (OAuthMessageSignerException e) { e.printStackTrace(); } catch (OAuthExpectationFailedException e) { e.printStackTrace(); } catch (OAuthCommunicationException e) { e.printStackTrace(); } //取得HTTP response try { response = new DefaultHttpClient().execute(post); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return response; } } 这样就完成了OAuth功能类的开发,后面都会用到这个类相关的方法。本篇到这里就算是完结请继续关注后面的文章。 上一篇讲了讲OAuth授权认证的事情,大概的介绍了OAuth的原理,并且完成了一个OAuth.java的类库,提供了几个OAuth认证必要的方法,本篇开始具体讲本项目的用户授权功能,用户授权页面是当用户第一次使用本软件的时候自动从载入页面跳转过来的显示的页面,涉及OAuth认证相关都是在上一篇的OAuth.java的类基础上开发。用户授权页面分为UI篇和功能篇两篇,本篇先来讲讲UI的实现,这次就不贴PS的效果图了直接贴实现后的功能截图如下:6 天前 上传下载附件 (84.47 KB)   看上面的图,其实这个页面的UI实现不复杂,首先是背景部分的实现这个参考 android开发我的新浪微博客户端-载入页面UI篇(1.1),重点来讲讲这个半透明的弹出对话框窗口是如何实现的,首先新建名为AuthorizeActivity.java的Activity,并且在AndroidManifest.xml文件中添加这个Activity,这样这个Activity才能被使用,接下来为这个Activity新建名为authorize.xml的Layout,这个Layout很简单只负责logo小图标显示,背景部分和透明窗口都是有代码来实现,所以非常简单参考 android开发我的新浪微博客户端-载入页面UI篇(1.1)。  完成Layout建立后在AuthorizeActivity的onCreate方法添加如下代码,设置authorize.xml为AuthorizeActivity的页面Layout: view plaincopy to clipboardprint? @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.authorize); ....... } 接下来是本文的重点部分,半透明弹窗用Dialog控件进行实现,首先为这个半透明弹窗新建一个名为dialog.xml的Layout,这个Layout主要是对4个元素进行布局,如图所示分别为i小图标、信息提示、中间文字、开始按钮,首先用LinearLayout对i小图标和信息提示进行水平布局,中间文字以一个TextView跟在下面,对于开始按钮是用RelativeLayout进行底部对齐显示。具体代码如下: view plaincopy to clipboardprint? 完成了半透明弹窗的Layout定义接下来我们要做的就是为它写一个自定义样式来实现我们想要的显示效果,首先我们需准备一个圆角的半透明png图片名为dia_bg.png并且添加到drawable中,接下来再res/values文件夹新建名为 dialogStyle.xml的resources样式文件,具体代码如下:view plaincopy to clipboardprint?

更多相关文章

  1. 安全新手入坑——HTML标签
  2. Nginx系列教程(四)| 一文带你读懂Nginx的动静分离
  3. Android(安卓)Activity 堆栈关系
  4. Android(安卓)全局消息通知框架实现(类似EventBus)
  5. Android开发指南-用户界面-通用布局对象
  6. Android开发——Accessibility机制实现模拟点击(微信自动抢红包实
  7. Android中menu使用详解
  8. Flutter 导航管理(一)
  9. 保护你的隐私,五种控制Android应用的权限的方法

随机推荐

  1. Android(安卓)Wi-Fi connect & auto conn
  2. 关于Android使用Google Map Android(安卓
  3. android添加删除桌面快捷方式
  4. Android编译时主要瓶颈分析
  5. SqliteDatabase
  6. android 后台长时间执行周期性定时任务
  7. Android(安卓)adb setuid提权漏洞的分析
  8. Android自定义Listener
  9. Android(安卓)Studio中debug和release模
  10. Android(安卓)之 ContentProvider 共享数