0%

今天下了个新浪微博的API研究研究,目前实现了发布微博功能,包括带图片的微博。为了安全,新浪微博的API中并没有提供用微博帐号密码登录的功能,而是采用OAuth授权,用户通过浏览器访问新浪网站登录,登录成功后,浏览器再返回key和secret给程序。 main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:id="@+id/login"
        android:text="登录" />
    <EditText android:id="@+id/status" android:layout_width="fill_parent"
        android:layout_height="300sp" android:hint="输入微博消息" />
    <Button android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:id="@+id/send"
        android:text="发布" />
</LinearLayout>

一个登录按钮,一个输入框,一个发布按钮 因为要接收浏览器返回的数据,所以,AndroidManifest.xml注册Activity的时候要加个Intent-Filter

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.pocketdigi.weibo" android:versionCode="1"
    android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Main" android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="sina" android:host="weibo" />
                <!-- 监控sina://weibo这样的地址 -->
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>

intent-filter必须分成两段写,如果合在一起写,就启动不了了。 为了简便,直接把新浪Sample里的OAuthConstant类拷过来:

package weibo4android.androidexamples;

import weibo4android.Weibo;
import weibo4android.http.AccessToken;
import weibo4android.http.RequestToken;

public class OAuthConstant {
    private static Weibo weibo=null;
    private static OAuthConstant instance=null;
    private RequestToken requestToken;
    private AccessToken accessToken;
    private String token;
    private String tokenSecret;
    private OAuthConstant(){};
    public static synchronized OAuthConstant getInstance(){
        if(instance==null)
            instance= new OAuthConstant();
        return instance;
    }
    public Weibo getWeibo(){
        if(weibo==null)
            weibo= new Weibo();
        return weibo;
    }
    
    public AccessToken getAccessToken() {
        return accessToken;
    }
    public void setAccessToken(AccessToken accessToken) {
        this.accessToken = accessToken;
        this.token=accessToken.getToken();
        this.tokenSecret=accessToken.getTokenSecret();
    }
    public RequestToken getRequestToken() {
        return requestToken;
    }
    public void setRequestToken(RequestToken requestToken) {
        this.requestToken = requestToken;
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
    public String getTokenSecret() {
        return tokenSecret;
    }
    public void setTokenSecret(String tokenSecret) {
        this.tokenSecret = tokenSecret;
    }
    
}

接下来就是最关键的主程序:

package com.pocketdigi.weibo;

import java.io.File;

import weibo4android.Weibo;
import weibo4android.WeiboException;
import weibo4android.http.AccessToken;
import weibo4android.http.RequestToken;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class Main extends Activity {
    /** Called when the activity is first created. */
    String key = "", secret = "";
    Button login,send;
    EditText status;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        System.setProperty("weibo4j.oauth.consumerKey", "3997936609");
        System.setProperty("weibo4j.oauth.consumerSecret",
                "8bc9e3bfd6ae8e3b2b8bda9079918950");
        //设置在新浪应用开放平台申请的应用的key和secret
        login=(Button)findViewById(R.id.login);
        send=(Button)findViewById(R.id.send);
        status=(EditText)findViewById(R.id.status);
        login.setOnClickListener(new OnClickListener(){

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                login();
                //登录
            }});
        send.setOnClickListener(new OnClickListener(){

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                String text=String.valueOf(status.getText());
                Weibo weibo =  new Weibo();
                weibo.setToken(key,secret);
                try {
                    //weibo.updateStatus(text);
                    //只发文字
                    File f=new File("/sdcard/wallpaper/129567208597069400.jpg");
                    weibo.uploadStatus(text,f );
                    //发文字+图片,这里需要导入commons-httpclient-3.0.1.jar,自己网上下
                    //在实际项目上,最好放Thread里,因为按下去的时候按钮会卡
                } catch (WeiboException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }});
    }

    @Override
    protected void onStart() {
        // TODO Auto-generated method stub
        super.onStart();
        //启动时执行检测是否来自网页登录返回
        //如果是,获取key和secret
        //否则读取SharedPreferences
        //若得不到key和secret,直接跳转登录
        Uri uri = this.getIntent().getData();
        if (uri != null) {
            //如果是浏览器返回
            try {
                RequestToken requestToken = OAuthConstant.getInstance()
                        .getRequestToken();
                AccessToken accessToken = requestToken.getAccessToken(uri
                        .getQueryParameter("oauth_verifier"));
                OAuthConstant.getInstance().setAccessToken(accessToken);
                // 保存
                Editor sharedata = getSharedPreferences("WeiBo", 0).edit();
                sharedata.putString("key", accessToken.getToken());
                sharedata.putString("secret", accessToken.getTokenSecret());
                sharedata.commit();
                key = accessToken.getToken();
                secret = accessToken.getTokenSecret();
            } catch (WeiboException e) {
                e.printStackTrace();
            }
        } else {
            //如果是用户自己启动
            SharedPreferences settings = getSharedPreferences("WeiBo", 0);
            key = settings.getString("key", "");
            secret = settings.getString("secret", "");
        }
        if (key.equals("") || secret.equals("")) {
            Toast.makeText(this, "尚未登录", Toast.LENGTH_LONG).show();
            login();
            //跳转到浏览器登录

        }

    }
    public void login(){
        Weibo weibo = OAuthConstant.getInstance().getWeibo();
        RequestToken requestToken;
        try {
            requestToken =weibo.getOAuthRequestToken("sina://weibo");
            //为了避免与同类应用冲突,还是自己改下URI吧
            Uri uri2 = Uri.parse(requestToken.getAuthenticationURL()+ "&from=xweibo");
            OAuthConstant.getInstance().setRequestToken(requestToken);
            startActivity(new Intent(Intent.ACTION_VIEW, uri2));
        } catch (WeiboException e) {
            e.printStackTrace();
        }
    }
}

发图片需要导入commons-httpclient-3.0.1.jar,否则启动报错,当然weibo4android-1.2.0.jar是不可少的

         一年以前,因为对当时收入的不满,以及对那份连临时工都不如的工作未来的迷茫,还有我对计算机的向往,我决定回头干IT。考虑到移动应用开发目前比较热门,而Android市场占有率也节节攀升,而且语言采用的是以前在大学听过几节课的Java,我毅然选择了Android.虽然早有预料干这行会很辛苦,不加班才不正常,但是对于一个自己感兴趣的工作,即使是天天加班也很快乐,因为我在做我喜欢做的事。          虽然有那么点Java基础,但是大学里真的学不到什么东西。 而且,我没碰Java都两年了,基本上都还给老师了(实在对不起左光华老师) ,所以一切都重头学起。每天下班后,还有周末,我都经常主动留在办公室帮同事值班,为的利用办公室的网络,学习Android。因为Android在界面方面用的是XML,Java基本上只用到最基础的知识,用不到Swing、SWT什么的,学习起来难度不大。             在一年以前,或者说一直到现在,很多人都认为Android不如IOS赚钱。有报告指出,超过60%的开发者没收入,剩余有收入的开发者中,有一半开发者表示收入很差或者不是很好。在这一点上,我觉得我是幸运的。在正式学习Android开发两个月以后(2010年9月),我发布了第一个应用,高清壁纸,就是发现现有的壁纸软件分辨率都太低,基本上都是适用于HVGA屏幕的,在我的MileStone效果很差,当时用的Wooboo广告平台(传应用送50RMB活动),到月低,差不多也有100块收入了(我明明设置了月底自动提现,但是那一百多块钱到现在好像还在Wooboo帐号里)。到了十月份,我把软件改成用WebView做UI(主要是因为我是新人,用Android做UI还不太会),把广告换成了adsense mobile(Google不允许把AdSense 移动广告放进应用里,不过我整个应用就是一浏览器,所以跟普通的网页基本没区别),收入达到了100多一点,当然,这是美元。没有什么比金钱的鼓励更实在了。接下来的几个月,我又更新了几次,到了今年1月,广告收入已经超过我本职工作的工资了,再加上近期我们领导干了几件不把我们当人看的事,我决定过完年辞职,专职做Android开发。2月份,过完年,顶着巨大的压力(几乎没有一个人支持)递了辞职报告。所谓有压力才有动力,我每天几乎从早上8点敲代码敲到晚上12点,发布了几个应用。到了4、5月份,收入差不多达到二级城市白领的标准了。但从6月份开始,一直到现在,adsense mobile单价暴跌,降到了0.02-0.04,以前可是0.07-0.1,收入严重缩水。最近发的几个应用全都换国内的广告平台,但是国内的平台十个九个黑。单价就一毛多一点,还扣量,最后还要扣6%的税,到手的没多少,感觉我们开发者完完全全就是给他们打工。            最后感谢所有来到我博客的朋友,这个博客建立之初只是用作学习记录,没想到有这么多朋友喜欢。到目前为止,在几乎没有任何宣传的情况下,流量达到了800IP,虽然以前建过差不多有十来个网站,但好像这个是除了音乐网站外,流量最高的了。虽然收入缩水,但我还是会坚持。这世上有多少人能把自己的爱好兴趣当成工作?我觉得我很幸福。也许有一天我坚持不了了,我也会找家公司继续干这行。           因为长期宅家不与人接触,语言表达能力退化,如果有看不懂,跳过吧。

NDK这东西如果是第一次用有点麻烦,我花了一天时间参考网上的文章,总算写出了Hello World.网上的文章都直接拷代码,没几篇写得具体点的。说明一下,对于C语言,我属于新手,怎么个新法?就是花了一两天时间,看了几节视频教程的那种。所以下面我的观点有什么错误,请见谅。本来想录个视频教程,但想想还是算了,我得把配置好的环境全删了再装,太麻烦,和我一样新或者比我还新的新手朋友如果看不懂,留言吧,我尽心帮忙。 第一步,下载安装NDK和Cygwin,Cygwin这东西其实就类似于一个Linux的模拟器,你要是用Linux开发,就不用装这东西了。 NDK:http://developer.android.com/sdk/ndk/index.html 貌似今天不用翻墙,网页上已经是r6版,我用的是r5 Cygwin:http://www.cygwin.com/setup.exe 是一个在线安装的文件,下载了打开 Cygwin安装时会提示选两个路径,第一个是安装路径,第二个是安装文件下载后存放路径,注意区别。下载站点选mirrors.163.com,贼快。Select Packages时勾选devel里的make、gcc、gcc-core、gcc-g++这个不是很确定,反正我是把所有描述为C compiler,C++ complier的都选了。 我的安装路径: NDK(解压就好):D:\ndk\android-ndk-r5c Cygwin:D:\ndk\cygwin 安装好后在开始菜单可以找到Cygwin Bash Shell,当然,你安装的时候要是没选就没有,那么就打开安装目录的Cygwin.bat文件。试一下分别输入gcc -v, g++ -v, make -v这几个命令,看看是不是都返回了正确的版本号。 上个图先: 再配置一下环境变量,编译的时候方便。打开D:\ndk\cygwin\home\FHP\.bash_profile(我电脑登录用户名为FHP),在文件最后添加: NDK="/cygdrive/d/ndk/android-ndk-r5c" export NDK Cygwin会把所有的硬盘分区映射到/cygdrive/目录下,所以,其实这里设置的就是上面的NDK路径 接下来,Eclipse需要安装CDT,在线安装http://www.eclipse.org/cdt/downloads.php ,根据你的版本选一个安装地址,与安装语言包类似,不明白请参考http://www.pocketdigi.com/20110204/165.html  装完后,新建一个Android项目,我这里为NDKTest,包为com.ndk,类为Main。我的项目路径:D:\ndk\NDKTest 建好项目后,在项目上点右键,新建一个名为jni的文件夹,所有的c或c++文件必须放这里。 下面配置一下Eclipse,让它自动编译我们的c或者c++源代码。在项目上点右键-属性,选择构建器,点右边的新建-程序,看图: 位置跟工作目录就不解释了,自变量解释一下,这个就是编译时执行的命令,/cygdrive/d/ndk/NDKTest是Android项目的路径,$NDK/ndk-build,就是D:\ndk\android-ndk-r5c\ndk-build 因为我们刚才设置了环境变量。 所以这个自变量的意思就是进入项目所在目录,执行ndk-build编译。 刷新选项卡要勾选“完成时刷新资源” 构建选项卡: 点击“指定资源”,勾选项目NDKTest的jni目录,这样,只要是jni下面的文件改变了,就会自动重新编译。 最后,保存,把刚才建的构建器移动最顶端(必须先编译c文件才能编译apk). 然后是代码部分,在jni目录下建一个ndkc.c文件,把c语言的代码都写这里:

#include 
#include 

jstring Java_com_ndk_Main_StringBack(JNIEnv* env, jobject obj){
     return (*env)->NewStringUTF(env, "TESTsfsfdaas标杆s!");
     //(*env)->NewStringUTF方法是把c语言的字符串转换成jstring(Java语言的字符串)
     //
}
//函数名有限制,必须为Java_调用它的类名_调用方法,JNIEnv* env, jobject obj,这两个参数是必须加的
jint Java_com_ndk_Main_Add(JNIEnv * env, jobject obj, jint a, jint b){
    return a+b;
    //好像基本数据类型不用转
    //jint就是int
}

在jni目录下建一个Android.mk文件,这个差不多就是编译的参数: LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ndkc LOCAL_SRC_FILES := ndkc.c #LOCAL_MODULE 指的是编译后的库名,java文件中直接就调用这个名字 #LOCAL_SRC_FILES c语言源文件名 include $(BUILD_SHARED_LIBRARY) 接下来就是Java里面的调用,Main.java:

package com.ndk;

import android.app.Activity;
import android.os.Bundle;

public class Main extends Activity {
    /** Called when the activity is first created. */
    static {
        System.loadLibrary("ndkc");
        //调用编译C语言生成的库
    }
    public native String StringBack();
    //定义一下来自C的方法
    public native int Add(int a,int b);
    //同上,返回类型为int,参数是两个int
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        System.out.println(StringBack());
        System.out.println(Add(5,6));
      //输出返回值
    }
}

最后是运行,可以在logcat看到结果 网上很多文章都说编译C文件之前要用javah 生成h头文件,但我好像都没用到,我感觉那一步唯一的作用就是可以参考生成的h头文件来写c程序,因为h头文件里包括了所有被java调用的方法包括及参数,返回类型这类的东西。如果要生成h头文件,注意一下,先编译,然后切换到项目的bin目录(不是src)下,再javah -包名.类名,在bin目录下就会生成一个h头文件

今天的程序可以实现电话状态改变时启动(来电、接听、挂断、拨打电话),但是暂时没法实现拨打电话时判断对方是否接听、转语音信箱等。Android在电话状态改变是会发送action为android.intent.action.PHONE_STATE的广播,而拨打电话时会发送action为android.intent.action.NEW_OUTGOING_CALL的广播,但是我看了下开发文档,暂时没发现有来电时的广播。知道这个就好办了,我们写个BroadcastReceiver用于接收这两个广播就可以了。

package com.pocketdigi.phonelistener;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;

public class PhoneReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
        System.out.println("action"+intent.getAction());
        if(intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)){
            //如果是去电(拨出)
            System.out.println("拨出");
        }else{
            //查了下android文档,貌似没有专门用于接收来电的action,所以,非去电即来电
            System.out.println("来电");
            TelephonyManager tm = (TelephonyManager)context.getSystemService(Service.TELEPHONY_SERVICE);   
            tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
            //设置一个监听器
        }
    }
    PhoneStateListener listener=new PhoneStateListener(){

        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            // TODO Auto-generated method stub
            //state 当前状态 incomingNumber,貌似没有去电的API
            super.onCallStateChanged(state, incomingNumber);
            switch(state){
            case TelephonyManager.CALL_STATE_IDLE:
                System.out.println("挂断");
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                System.out.println("接听");
                break;
            case TelephonyManager.CALL_STATE_RINGING:
                System.out.println("响铃:来电号码"+incomingNumber);
                //输出来电号码
                break;
            }
        }
        
    };
}

要在AndroidManifest.xml注册广播接收器:

        <receiver android:name=".PhoneReceiver">
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE"/>
        <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
            </intent-filter>
        </receiver>

还要添加权限:

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

画android机器人很简单,就是几个圆角距形和几个圆形的组合,难点在于机器人头顶上的两根天线,涉及到旋转画布,需要通过不断地调整参数来定位到所需的位置。 先看效果图: Java代码:

package com.pocketdigi;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.View;

public class Main extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        RobotView cv = new RobotView(this);
        setContentView(cv);
    }

}
class RobotView extends View {
    public RobotView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }
    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        //背景为白色
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        //抗锯齿
        paint.setColor(Color.GREEN);
        //画笔颜色为绿色

        canvas.rotate(45.0f);
        //顺时针旋转画板45度
        canvas.drawRoundRect(new RectF(200, 0, 270, 10), 10, 10, paint);
        //左天线
        canvas.restore();
        //恢复canvas到原始状态
        canvas.rotate(-45.0f);
        canvas.drawRoundRect(new RectF(20, 285, 90, 295), 10, 10, paint);
        //右天线
        canvas.restore();
        canvas.drawCircle(200, 350, 100, paint);
        //画圆,下半圆直接被下面的身体覆盖
        canvas.drawRoundRect(new RectF(100, 350, 300, 530), 10, 10, paint);
        //画身体
        paint.setColor(Color.WHITE);
        //画笔改白色
        canvas.drawCircle(160, 300, 10, paint);
        //左眼
        canvas.drawCircle(240, 300, 10, paint);
        //右眼
        canvas.drawRect(new Rect(100,350,300,360), paint);
        //画白色距形,分开头跟身体
        paint.setColor(Color.GREEN);
        //换回绿色
        canvas.drawRoundRect(new RectF(60, 360, 90, 500), 10, 10, paint);
        //左手
        canvas.drawRoundRect(new RectF(310, 360, 340, 500), 10, 10, paint);
        //右手
        canvas.drawRoundRect(new RectF(130, 520, 160, 620), 10, 10, paint);
        //左脚
        canvas.drawRoundRect(new RectF(240, 520, 270, 620), 10, 10, paint);
        //右脚
        
    }

}

sax是通过事件来解析xml的,它并不会像DOM一样直接一开始就把整个文档读入,而是解到哪,读到哪。这样,当目标xml文件很大的时候,也不会占用很多内存。 简单介绍一下几个主要事件,解析的时候一般就按下面的顺序执行 StartDocument:开始解析文档 StartElement:开始读取元素 characters:读取字符(就是元素内的内容) EndElement:读取元素结束 endDocument:文档解析结束 其实sax的原理很简单,当读到指定的位置时,触发相应的方法,我们就可以通过重写该方法来记录xml内的数据。 下面开始上实例,还是解析我博客的RSS,地址 http://www.pocketdigi.com/feed, 目标是读取文章标题 首先我们要先自定义一个类,继承DefaultHandler(sax的事件处理类),重写以上事件触发的方法:

package com.pocketdigi;

import java.util.ArrayList;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class XmlHandler extends DefaultHandler {
    String tempName;
    //临时变量用于存储当前读取的标签
    ArrayList temparr=new ArrayList();
    //临时字符串数组用于存储读到的字符串
    ArrayList titles=new ArrayList();
    //存储读到的文章标题
    boolean flag=false;
    //因为博客名称跟标题的标签都是title,所以定时一个flag来区别是博客名称还是文章标题
    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        // TODO Auto-generated method stub
        super.characters(ch, start, length);
        if(tempName.equals("title")&&flag){
            temparr.add(new String(ch,start,length));
        }
        //当前标签是title,并且在item内时,把读到的字符转换成字符串存进临时数组
        //之所以这样做,是回为当要读的标签内容过长时,characters方法不能一次把所有的字符都读进来,而是分多次读
    }

    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        // TODO Auto-generated method stub
        super.endElement(uri, localName, qName);
        if(qName.equals("title")&&flag){
            //title标签读取结束时,把temparr转换成string,存进titles
            String title="";
            for(int i=0;i

`然后在程序中调用:

package com.pocketdigi;

import java.io.StringReader;
import java.util.ArrayList;

import javax.xml.parsers.SAXParserFactory;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

public class Main {
    static ArrayList list;

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String content=GetHtml("http://www.pocketdigi.com/feed");
        //获取源码
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            XMLReader reader = factory.newSAXParser().getXMLReader();
            XmlHandler xmlhandler = new XmlHandler();
            reader.setContentHandler(xmlhandler);
            reader.parse(new InputSource(new StringReader(content)));
            ArrayList titles=xmlhandler.titles;
            for (String s:titles){
                //循环输出标题
                System.out.println(s);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        
  
    }
            //下载源码的方法
    public static String GetHtml(String url){
        try {
            String html="";
            HttpGet httpget=new HttpGet(url);
            DefaultHttpClient httpclient=new DefaultHttpClient();
            HttpResponse response=httpclient.execute(httpget);
            if(response.getStatusLine().getStatusCode()==200){//如果状态码为200,就是正常返回
                html=EntityUtils.toString(response.getEntity());
            }

            return html;  
        } catch (Exception e) {
            // TODO Auto-generated catch block
            return "error open url:" + url;  
        } 
    }

}`

HttpURLConnection的父类URLConnection有setConnectTimeout和setReadTimeout方法,分别用于设置连接和读取超时,因为子类继承了父类的方法,所以我们也可以直接用

URL newurl = new URL(url);
HttpURLConnection httpconn = (HttpURLConnection) newurl.openConnection();
httpconn.setConnectTimeout(1000);
httpconn.setReadTimeout(1000); 

后面参数为超时时间,单位毫秒。一旦达到超时时间,会抛出java.net.SocketTimeoutException异常

Android默认没有安装TTS数据包,无法文字转语音,而在设置里推荐的语音包是Pico TTS,并不支持中文,如果需要读中文,需要下载另外的第三方语音包,如:eSpeak,Svox,个人建议Svox,eSpeak非常生硬,而且很多汉字都读不出,不支持中英文混读(英文当拼音读)。下载链接自己Google下吧,很多软件市场都有下载。下载安装后打开设置-语音输入和输出-文字转语音设置,勾选Svox Classic TTS,语言选择中文或广东话(Svox安装后还要下载安装相应的语言包的,类似插件)。接下来代码就简单了,关键的就两三行:

package com.pocketdigi;

import android.app.Activity;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class TTSActivity extends Activity {
    /** Called when the activity is first created. */
    TextToSpeech tts;
    Button btn;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btn=(Button)findViewById(R.id.btn);
        tts = new TextToSpeech(this, null);
        //实例化
        btn.setOnClickListener(new OnClickListener(){

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                tts.speak("测试一下", TextToSpeech.QUEUE_FLUSH, null);
                //语音输出
            }});
        
    }
}

用Svox,效果还不错。 2011年7月21日加注: 貌似使用Svox后,isLanguageAvailable就不能检测语言是否支持了,而Pico TTS是可以的

Android继承了Linux权限严格的优点,很多命令需要root权限才能执行,比如说查看系统目录文件等,如果我们的程序需要用到查看此类文件或执行系统命令,就必须先获取Root权限。网上看了很多版本的检测是否有Root权限的方法,结果不论是否有权限,都返回true,根本就没有用,下面的方法经真机检测确定可行。 上代码:

package com.pocketdigi;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import android.app.Activity;
import android.os.Bundle;

public class RootActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        DataInputStream stream;
        if(isRooted()){
        try {
            stream = Terminal("ping -c 2 www.pocketdigi.com");
            //其实ping并不需要root权限 ,这里是ping 2次后才停止,所以启动后需要一点时间才会有显示
            //你可以自己换成需要root权限的命令试试
            String temp;
            while((temp=stream.readLine())!=null){
                System.out.println(temp);
                //循环输出返回值
            }
        } catch (Exception e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        }
    }
    public DataInputStream Terminal(String command) throws Exception
    {
        Process process = Runtime.getRuntime().exec("su");
        //执行到这,Superuser会跳出来,选择是否允许获取最高权限
        OutputStream outstream = process.getOutputStream();
        DataOutputStream DOPS = new DataOutputStream(outstream);
        InputStream instream = process.getInputStream();
        DataInputStream DIPS = new DataInputStream(instream);
        String temp = command + "\n";
        //加回车
        DOPS.writeBytes(temp);
        //执行
        DOPS.flush();
        //刷新,确保都发送到outputstream
        DOPS.writeBytes("exit\n");
        //退出
        DOPS.flush();
        process.waitFor();
        return DIPS;
    }
    public boolean isRooted() {
        //检测是否ROOT过
        DataInputStream stream;
        boolean flag=false;
        try {
            stream = Terminal("ls /data/");
            //目录哪都行,不一定要需要ROOT权限的
            if(stream.readLine()!=null)flag=true;
            //根据是否有返回来判断是否有root权限
        } catch (Exception e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
            
        }
        
        return flag;
    }


}

虽然Android也是Linux,但是却没有shutdown命令 关机和重启命令需要root权限执行。 关机:poweroff -f(不加f参数关不了) 重启:reboot 2011年9月16日加注:CyanogenMod 的ROM 好像没有poweroff命令