0%

AppWidget要向外部发送数据,可以把数据放在Intent里,再用intent对象生成一个PendingIntent对象,然后用RemoteViews的setOnClickPendingIntent绑定到相应控件上,具体代码如下:

RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.widget);
ComponentName thisWidget = new ComponentName(context,Widget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(context);
Intent intent=new Intent(context,Main.class);
Bundle extras=new Bundle();
extras.putInt("appWidgetId",  appWidgetIds[0]);
intent.putExtras(extras);
PendingIntent pendingIntent=PendingIntent.getActivity(context,0, intent,PendingIntent.0);
updateViews.setOnClickPendingIntent(R.id.abs,pendingIntent);
manager.updateAppWidget(thisWidget, updateViews);

网上的例子代码基本上是这样的,但是如果在启动的Activity接收Intent过来的数据,你会发现得到的Bundle其实是空的,也就是说,根本没有数据传过来。 这里我们需要改一下第8行代码,getActivity方法的最后一个参数是int flag,根据官方开发指南,这个值可以是FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT 简单翻译一下: int FLAG_CANCEL_CURRENT:如果该PendingIntent已经存在,则在生成新的之前取消当前的。 int FLAG_NO_CREATE:如果该PendingIntent不存在,直接返回null而不是创建一个PendingIntent. int FLAG_ONE_SHOT:该PendingIntent只能用一次,在send()方法执行后,自动取消。 int FLAG_UPDATE_CURRENT:如果该PendingIntent已经存在,则用新传入的Intent更新当前的数据。 我们需要把最后一个参数改为PendingIntent.FLAG_UPDATE_CURRENT,这样在启动的Activity里就可以用接收Intent传送数据的方法正常接收。

Vibrator可以控制手机的震动器,实现简单的持续震动以及周期振动。 布局XML代码不贴了,就一个ToggleButton.下面是JAVA代码:

package com.pocketdigi.virbrate;

import android.app.Activity;
import android.app.Service;
import android.os.Bundle;
import android.os.Vibrator;
import android.widget.CompoundButton;
import android.widget.ToggleButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

public class Main extends Activity {
    /** Called when the activity is first created. */
    ToggleButton tb;
    Vibrator vt;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        vt=(Vibrator) getApplication().getSystemService(Service.VIBRATOR_SERVICE);
        //得到Vibrator对象
        tb=(ToggleButton)findViewById(R.id.tb);
        tb.setOnCheckedChangeListener(new OnCheckedChangeListener(){

            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                // TODO Auto-generated method stub
                if(isChecked){
                    //vt.vibrate(1000);
                    //最简单的震动,后面参数为振动持续的时间
                    long[] l=new long[]{1000,5000,2000,1000,1000,500};
                    //周期振动,索引0或偶数为间隔时间,索引为单数为振动时间(毫秒)
                    vt.vibrate(l,1);
                    //传入long数组,和重复振动开始的索引,这里是1,就是第二次执行的是{5000,2000,1000,1000,500}这样一个数组
                }else{
                    vt.cancel();
                }
            }
            
        });
    }
}

已经加了注释,不再解释。

ToggleButton是一种带状态的Button,有ON,或OFF状态,效果如下图 XML代码:

<ToggleButton android:id="@+id/tb" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content"
android:textOn="开"
android:textOff="关"
android:checked="true"
 />

textOn 按钮开启时显示的文本 textOff 按钮关闭时显示的文本 checked 载入时的状态,默认为false,即关 JAVA代码

package com.pocketdigi.togglebutton;

import android.app.Activity;
import android.os.Bundle;
import android.widget.CompoundButton;
import android.widget.Toast;
import android.widget.ToggleButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

public class Main extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ToggleButton tb=(ToggleButton)findViewById(R.id.tb);
        tb.setOnCheckedChangeListener(new OnCheckedChangeListener(){

            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                // TODO Auto-generated method stub
//				isChecked就是按钮状态
                if(isChecked){
                    Toast.makeText(Main.this,"打开",Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(Main.this,"关闭",Toast.LENGTH_LONG).show();

                }
            }
            
        });
    }
}

如果需要调用系统短信功能发送短信,参考:http://www.pocketdigi.com/20101005/122.html 调用系统电话程序跟调用短信类似,关键在于Intent.

Intent intent=new Intent("android.intent.action.CALL",Uri.parse("tel:"+num));
startActivity(intent);

num即电话号码。 下面是完整的代码:

package com.pocketdigi.call;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.app.Activity;
import android.content.Intent;
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. */
    EditText et;
    Button bt;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        et=(EditText)findViewById(R.id.et);
        bt=(Button)findViewById(R.id.bt);
        bt.setOnClickListener(new OnClickListener(){

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                String num=et.getText().toString();
                Pattern p = Pattern.compile("\d+?");
                Matcher match = p.matcher(num);
                //正则验证输入的是否为数字
                if(match.matches()){
                    Intent intent=new Intent("android.intent.action.CALL",Uri.parse("tel:"+num));
                    startActivity(intent);
                }else{
                    Toast.makeText(Main.this, "号码不对",Toast.LENGTH_LONG).show();
                }
            }
            
        });
    }
}

注意在AndroidManifest.xml添加拨打电话权限:

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

布局xml就不贴了,一个EditText,一个Button. 完整程序: [download id=”18”]

花了几天时间做了个查看网站统计的APP,暂时命名为站长工具箱,目前版本为预览版,只支持51.la统计,下一版本支持更多主流统计站点。因为相对其他APP,使用起来有些复杂,所以贴上图文使用教程。 Android站长工具箱下载地址: http://app.pocketdigi.com/com.pocketdigi.webmaster.html 第一步,获取站点ID和查看密码。 登录后台,点参数设置, 设置独立查看密码,公开程度可以随意设置。 根据浏览器地址栏显示的地址,得到站点ID 第二步,在手机上安装APP,输入刚才得到的查看密码,站点ID,保存 第三步,添加主屏幕小组件,按Home键或back键切换到主屏幕,再按menu键,弹出下图菜单,点添加 选择小组件 选择站长工具箱 最终效果图: 数据每30分钟自动更新一次,添加主屏幕小组件后第一次更新会有所延迟,但一般在1小时内。

如果你的APP有搜索数据的功能,可以给程序加个类似于系统自带的搜索功能,按搜索键,会在屏幕顶部弹出一个输入框,可以搜索短信、音乐、联系人、网页等等。下面就来实现。 首先,我们要在AndroidManifest.xml给我们的Activity加上一个intent-filter:

            <intent-filter>
                <action android:name="android.intent.action.SEARCH"></action>
                <category android:name="android.intent.category.DEFAULT"></category>
            </intent-filter>
            <!-- 上面的intent-filter就是用来匹配搜索功能的,下面一行指定的是搜索框配置文件 -->
            <meta-data android:name="android.app.searchable" android:resource="@xml/search"/>

这样,当用户按下搜索键时,系统就会弹出搜索框以及软键盘,等待用户输入。 下面是xml/search.xml源代码(新键-Android XML File,选择Searchable就可以创建):

<?xml version="1.0" encoding="utf-8"?>
<searchable
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:label="@string/search_label"
  android:hint="@string/search_hint"
  >
</searchable>
<!-- 
label:标签,可以任意填
hint:输入框中的提示
经测试,这两个值必须引用string,直接写死就不会跳出搜索框
 -->

上面的两个值先写进strings.xml中。 下面是程序中获取用户输入字符串的方法:

package com.pocketdigi.search;

import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;

public class Main extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Intent intent = getIntent();
        //得到Intent对象
        String action = intent.getAction();
        //得到action值
        if (Intent.ACTION_SEARCH.equals(action))
        {//如果是搜索的action
          String query = intent.getStringExtra(SearchManager.QUERY);
          //得到查询关键字
          System.out.println(query);
          
        }
    }
}

另外,如果需要在程序中触发弹出搜索框而不是按Search键,可以使用下面的Java代码

onSearchRequested();

附上打包源文件: [download id=”17”]

2011.05.23 更新: 经过半天的测试,确定1.6以上版本Widget更新时间为半小,但是添加Widget后第一次启动有延迟,是37分左右,后面每次更新都是间隔30分钟。 想必大家都用过桌面小组件,像天气预报,时钟之类的,与Activity不同,它的主体是一个显示在桌面上可移动的视图,而并不是一个独立的程序。 先看例子的效果图: 不太好看,但是没关系,UI不是重点,重点是怎么把它写出来。 解释一下,一个红色的界面,上面有个TextView,显示一个数字,这个数字每秒加1,点击数字会跳出一个Activity.OK,下面来实现。 先介绍下结构: Main.java 主程序,一个Activity,点击桌面的Widget上文字后跳出,也可以在程序列表上点图标启动。 UpdateService.java 一个更新桌面Widget的服务Service,每隔一秒更新视图 Widget.java 这个才是桌面Widget需要用到的程序,继承自AppWidgetProvider layout/main.xml Main Activity和Widget用到的布局文件,Activity和Widget都用这个作而已(简单起见) xml/widget.xml Widget的配置文件,配置显示的宽、高,更新时间,指定布局文件(main.xml) 下面贴代码: 先是AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.pocketdigi.widget" android:versionCode="1"
    android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <receiver android:label="@string/app_name" android:name=".Widget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data android:resource="@xml/widget" android:name="android.appwidget.provider" />
        </receiver>
        <activity android:name=".Main">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".UpdateService" />
    </application>
</manifest>

AppWidgetProvider类继承于BroadcastReceiver,所以这里定义的时候跟BroadcastReceiver是一样的,用receiver标签 android.appwidget.action.APPWIDGET_UPDATE是固定值,在添加widget到桌面时会发送该广播 android.appwidget.provider设置的是AppWidgetProvider配置的XML文件 再下面是一个actvity,一个service,不再解释 Main.java就不贴了,在这里就是自动生成的Hello World Widget.java:

package com.pocketdigi.widget;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;

public class Widget extends AppWidgetProvider {

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        // TODO Auto-generated method stub
        //添加到桌面和更新界面时执行,但是1.6以后没有了updatePeriodMillis属性,所以会看不到更新界面执行的效果
        System.out.println("onUpdate");
        Intent intent=new Intent(context,UpdateService.class);
        context.startService(intent);
        //启动服务
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // TODO Auto-generated method stub
        //被删除时执行
        System.out.println("删除");
        Intent intent=new Intent(context,UpdateService.class);
        context.stopService(intent);
        //停止后台服务
        super.onDeleted(context, appWidgetIds);
    }
    
}

UpdateService.java;

package com.pocketdigi.widget;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.widget.RemoteViews;

public class UpdateService extends Service {
    boolean is_run = false;
    boolean flag=true;
    //标记线程是否已经启动,已启动就不会再次启动
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void onStart(Intent intent, int startId) {
        // TODO Auto-generated method stub
        super.onStart(intent, startId);
        final RemoteViews updateViews = new RemoteViews(this.getPackageName(),
                R.layout.main);
        final ComponentName thisWidget = new ComponentName(this, Widget.class);
        final AppWidgetManager manager = AppWidgetManager.getInstance(this);

        if (!is_run) {
            //如果已经启动,不执行
            new Thread() {
                //因为XML中的updatePeriodMillis属性无效,为了测试更新界面的效果
                // 所以,我在这新建了一个线程,在里面无限循环更新APP Widget
                //第一天学这个,不知道这样会不会很费电,不知道还有什么其他方法,以后如果发现再补充
                public void run() {
                    int i = 0;
                    is_run=true;
                    while (flag) {
                        updateViews.setTextViewText(R.id.tv, String.valueOf(i));
                        updateViews.setInt(R.id.tv, "setTextColor",
                                R.color.black);
                        updateViews.setFloat(R.id.tv, "setTextSize", 20.0f);
                        // setXX方法,三个参数分别为操作控件在XML中的ID,
                        // 执行的方法名(该控件ID通过findViewById得到的view,该view的方法,上面执行的就是TextView的setTextColor和setTextSize方法)
                        // 方法的参数,setXX需要根据该参数类型确定,如setTextColor的参数是int,就调用setInt,setTextSize的参数是Float,就调用setFloat
                        Intent intent = new Intent(UpdateService.this,
                                Main.class);
                        PendingIntent pendingintent = PendingIntent
                                .getActivity(UpdateService.this, 0, intent, 0);
                        updateViews.setOnClickPendingIntent(R.id.tv,
                                pendingintent);
                        // 以上几行相当于调用TextView的setOnClickListener方法,在点击时启动Main这个Activity
                        manager.updateAppWidget(thisWidget, updateViews);
                        // 更新
                        i++;
                        try {
                            sleep(1000);
                            // 休息1秒
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }

    }

    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        System.out.println("onDestory");
        flag=false;
        //服务停止时停止线程
        super.onDestroy();
    }


}

Main.xml,其实就是把自动生成的Hello World的TextView加个ID:

<?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"
    android:background="@color/red"
    >
<TextView  android:id="@+id/tv"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

widget.xml:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:minWidth="50dip"
  android:minHeight="50dip"
  android:updatePeriodMillis="5000"
  android:initialLayout="@layout/main"
  >
</appwidget-provider>

前两个分别是宽,高的最小值,我在测试的时候,没发现修改这两个值对Widget显示的宽高有什么影响,希望知道的同学留下言 updatePeriodMillis更新间隔时间,单位毫秒,根据实际测试,以及网上的资料,如果android版本在1.6及以上,不支持这个属性,好像默认半小时左右更新 最后是Widget小组件的界面xml文件 另外,在strings.xml中加了两个颜色值:

    <color name="red">#ff0000</color>
    <color name="black">#000000</color>

OK,就是这样,测试一下吧!附上打包的源代码,为了提高人气,回得可见,望理解: [download id=”16”]

因为代码与Java用apache的HttpClient发送Post请求大部份重复,所以就不贴整段代码了,只把不同的地方贴出来。 发送Cookie就必须先得到Cookie,所以至少发送两次请求,第一次用于得到Cookie,第二次在发送请求前加上Cookie 在第一次发送Post请求前,先建立一个DefaultHttpClient对象的引用,在上文中没有建立引用,new了一个DefaultHttpClient对象后直接使用。既然要发送Cookie,必然先要得到Cookie,要得到cookie就需要DefaultHttpClient.在第一次发送请求后,就可以使用DefaultHttpClient对象的getCookieStore(),得到一个CookieStore对象,我们用到的Cookie就存在这里。还是贴一下这几句代码: 上文37行作如下修改:

DefaultHttpClient httpclient=new DefaultHttpClient();
HttpResponse response=httpclient.execute(httppost);
CookieStore cookiestore=httpclient.getCookieStore();
//得到Cookie

第二次请求,把第一次请求的代码再复制一次。当然,变量名会重复,改一下即可。现在要在发送请求之前加上刚才得到的cookie,还是改上文的37行:

DefaultHttpClient httpclient2=new DefaultHttpClient();
httpclient2.setCookieStore(cookiestore);
//把第一次请求的cookie加进去
HttpResponse response2=httpclient2.execute(httppost2);

发送POST请求方法参考http://www.pocketdigi.com/20110521/294.html 相对于POST,接收文件稍稍修改一下:

<?php
@$usr=$_GET['usr'];
@$pwd=$_POST['pwd'];
echo $usr;
echo $pwd;
?>

当然,在实际项目中,没有特别需要,不会用GET接收用户名,同时用POST接收密码,我这只是能用POST和GET的例子都能正常运行。 Java Get的代码,与Post类似

package com.pocketdigi;

import java.io.UnsupportedEncodingException;

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;
/**
*JDK默认没有org.apache.http包,需要先去http://hc.apache.org/downloads.cgi下载
*下载HttpClient,解压,在Eclipse中导入所有JAR
*/
public class Main {
    /**
     * @param args
     * @throws UnsupportedEncodingException 
     * 这个例子为了简单点,没有捕捉异常,直接在程序入口加了异常抛出声明
     */
    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        String url="http://localhost/newspaper/test/1.php?usr=156gs";
        //GET的URL,参数直接加URL后
        HttpGet httpget=new HttpGet(url);
        //建立HttpPost对象
        HttpResponse response=new DefaultHttpClient().execute(httpget);
        //发送GET,并返回一个HttpResponse对象,相对于POST,省去了添加NameValuePair数组作参数
        if(response.getStatusLine().getStatusCode()==200){//如果状态码为200,就是正常返回
            String result=EntityUtils.toString(response.getEntity());
            //得到返回的字符串
            System.out.println(result);
            //打印输出
        }
    }
}

要获取网络上的网页内容有POST,和GET两种方式,Get比较简单,直接把参数放在URL结尾就OK,比如http://127.0.0.1/list.php?id=1这个URL,问号后面的就是传送的参数,id为1。但是get有个受到浏览器支持的URL最大长度的限制,而且如果传用密码之类的东西,直接写在网址里也不安全。Post相对于Get没有长度限制,也不会把数据明文放在URL结尾。 下面的例子是用Java发送Post请求,并把网页返回的内容输出。 首先看接收请求的php文件源代码:

<?php
@$pwd=$_POST["pwd"];
echo $pwd;
?>

很简单,功能就是把Post过来的pwd值输出。 下面是发送POST的JAVA代码:

package com.pocketdigi;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
/**
*JDK默认没有org.apache.http包,需要先去http://hc.apache.org/downloads.cgi下载
*下载HttpClient,解压,在Eclipse中导入所有JAR
*/
public class Main {
    /**
     * @param args
     * @throws UnsupportedEncodingException 
     * 这个例子为了简单点,没有捕捉异常,直接在程序入口加了异常抛出声明
     */
    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        String url="http://localhost/newspaper/test/1.php";
        //POST的URL
        HttpPost httppost=new HttpPost(url);
        //建立HttpPost对象
        List params=new ArrayList();
        //建立一个NameValuePair数组,用于存储欲传送的参数
        params.add(new BasicNameValuePair("pwd","2544"));
        //添加参数
        httppost.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8));
        //设置编码
        HttpResponse response=new DefaultHttpClient().execute(httppost);
        //发送Post,并返回一个HttpResponse对象
                //Header header = response.getFirstHeader("Content-Length");
        //String Length=header.getValue();
                // 上面两行可以得到指定的Header
        if(response.getStatusLine().getStatusCode()==200){//如果状态码为200,就是正常返回
            String result=EntityUtils.toString(response.getEntity());
            //得到返回的字符串
            System.out.println(result);
            //打印输出
                       //如果是下载文件,可以用response.getEntity().getContent()返回InputStream
        }
    }
}

2011年5月24日注:处理乱码,对取得的result字符串作下转换,

result=new String(result.getBytes("ISO-8859-1"),"GBK")

网页编码为GBK