2015年7月31日星期五

定制自己的Log日志工具

为了方便调试,编程的时候写入了很多的Log。如果项目完成的时候,一一删除是一个头疼的事情,可是不删除的话又会输出大量我们不愿展现出来的信息,可能会是机密,况且这也很拖累运行效率。

so, what should we do?

Is there anyway to solve this problem?

很简单,把所有的Log都写进一个单独的类中,类中实现自定义方法用于输出,然后项目中再需要打印的时候再调用此类中方法。不用的时候,单独处理此类即可。貌似描述得有点干吧,上代码!

import android.util.Log;

public class LogUtil {
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static final int LEVEL = VERBOSE;

public static void v(String tag, String msg) {
if (LEVEL <= VERBOSE) {
Log.v(tag, msg);
}
}

public static void d(String tag, String msg) {
if (LEVEL <= DEBUG) {
Log.d(tag, msg);
}
}

public static void i(String tag, String msg) {
if (LEVEL <= INFO) {
Log.i(tag, msg);
}
}

public static void w(String tag, String msg) {
if (LEVEL <= WARN) {
Log.w(tag, msg);
}
}

public static void e(String tag, String msg) {
if (LEVEL <= ERROR) {
Log.e(tag, msg);
}
}

}

在LogUtil中定义了VERBOSE、DEBUG、INFO、WARN、ERROR、NOTHING这六个整型常量,其对应的值都是递增的。然后定义一个LEVEL常量,其值可以指定为上面六常量之一。

LogUtil实现了v(),d(),i(),w(),e()五个自定义的Log方法,在其内部分别调用Log.v()等来打印日志。在自定义的方法中加入了if条件判断,当LEVEL常量满足某一条件时才会打印。

这就是Log日志工具。

在项目中,当需要打印DEBUG级别的日志时可以这样写,

LogUtil.d("TAG", "debug log");

控制LEVEL的值可以控制日志的打印行为,如将LEVEL定义为VERBOSE可以将所有的日志都打印出来,使其等于WARN可以将警告以上级别的日志打印出来,使其等于NOTHING可以将所有的日志都屏蔽掉,这就解决了文前提高的问题。

LOL !




Android中的context浅析

在Android系统中,有很多的service。我们的程序如果用到系统功能,一般都是调用服务间接完成的。也就是在Android系统中存在许多C/S架构。context的作用,就是android应用连接service的桥梁。比如Activity中有一个方法,getSystemService()。这个方法调到最后,实际上是调用的ContextImpl的getSystemService()方法,而ContextImpl是对Context的实现

      Context不是函数而是一个类——如果不太了解面向对象,可以把“类”看做一种数据类型,就像int,不过类型为“类”的数据(称为对象)可能储存远比int多的信息,比如这里的类型为Context的对象就储存关于程序、窗口的一些资源

        有些函数调用时需要一个Context参数,比如Toast.makeText,因为函数需要知道是在哪个界面中显示的Toast

        再比如,Button myButton = new Button(this); 这里也需要Context参数(this),表示这个按钮是在“this”这个屏幕中显示的

        Android开发使用(纯粹的)面向对象语言,一切都是对象,就连我们写的函数都是对象的函数。

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(this,
                "OK!",
                Toast.LENGTH_LONG).show();
        Button button1 = (Button)findViewById(R.id.button1);
        button1.setOnClickListener(new Button.OnClickListener(){
            public void onClick(View v)
            {
                Toast.makeText(MainActivity.this,
                        "Hello, world!",
                        Toast.LENGTH_LONG).show();
            }
        });
    }
}
      这里OnCreate就是MainActivity的对象的函数(MainActivity是类),所以这个函数中的this就表示当前的、包含这个函数的MainActivity对象。

      MainActivity extends Activity,意思是MainActivity 继承 Activity,即MainActivity 是 Activity 的一种,所有的MainActivity 都是 Activity。同样,在Android文档中Activity继承ContextThemeWrapper,ContextThemeWrapper继承ContextWrapper,ContextWrapper继承Context。所以this这个MainActivity也是Context,把this传入Toast.makeText表示“OK!”是在当前的MainActivity对象(也是Context)中显示的。

      对于显示"Hello, world!"的Toast.makeText,这个函数在onClick中,而onClick是new Button.OnClickListener(){...}这个没有名字的类的函数,this表示匿名类的对象,不表示MainActivity对象,所以这里用MainActivity.this,强制选择外面一层MainActivity的this



2015年7月30日星期四

调用手机光照传感器测光强

娘希匹的,昨天就写好了这个blog,发布的时候翻墙失败,也没保存成功,现在重写。

简单调用手机的光照传感器来检测环境的光强,很easy哒。

Android的每个传感器的用法都很相似,首先要获取SensorManager的实例,

1.获取SensorManager实例

SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

2.调用具体的传感器

SensorManager是系统所有传感器的管理器,有了它的实例后就可以调用getDefultSensor()方法来得到任意的传感器类型,

Sesnor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);//这里是光照传感器

4.对传感器输出的信号进行监听

private SensorEventListener listener = new SensorEventListener() {

@Override
public void onSensorChanged(SensorEvent event) {
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};

这里的SensorEventListener接口定义了两个方法,当传感器精度发生变化时调用onAccuracyChanged方法,当传感器检测到的数值变化时调用onSensorChanged()方法。
onSensorChanged()方法传入了一个名为SensorEvent的参数,其中包含了一个values数组,所有传感器的信息都存放在values数组里。

5.注册传感器的监听器

接下来需要调用SensorManager的registerListener()方法来注册SensorEventListener才能使其生效,这里的registerListener()方法接收了三个参数,第一个为SensorEventListener的实例,第二个为Sensor实例,第三个为传感器输出信息的更新速度,有ENSOR_DELAY_UI、SENSOR_DELAY_NORMAL、SENSOR_DELAY_GAME和SENSOR_DELAY_FASTEST四个可选,它们的更新速度依次递增。

sensorManager.registerListener(listener, sensor,
SensorManager.SENSOR_DELAY_NORMAL);

这就是(光线)传感器的简单用法,接下来上实例吧~~

-----------------------------------------------------

1.布局文件只要一个TextView,简单粗暴,能显示光强即可。

    <TextView
        android:id="@+id/light_level"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        android:layout_centerInParent="true" 
        android:textSize="20sp"/>

2.MainActivity部分代码,

public class MainActivity extends Activity {

private SensorManager sensorManager;

private TextView lightLevel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lightLevel = (TextView) findViewById(R.id.light_level);// 获取TextView实例
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);// 获取光照传感器实例
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
sensorManager.registerListener(listener, sensor,
SensorManager.SENSOR_DELAY_NORMAL);// 注册光照传感器的监听器
}

@Override
protected void onDestroy() {
super.onDestroy();
if (sensorManager != null) {
sensorManager.unregisterListener(listener);// 释放传感器
}
}

private SensorEventListener listener = new SensorEventListener() {

@Override
public void onSensorChanged(SensorEvent event) {
// values数组中第一个下标的值就是当前的光照强度
float value = event.values[0];
lightLevel.setText("Current light level is " + value + "lx");
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 当传感器光强变化时调用
}
};
}

注意,这里的values中只有一个值——光强,存储在values[0]中。

3.运行之



595.01lx(勒克斯),这个值是我测到的座位附近最大值,它是不断变化的呦,这个要感谢可爱的onSensorChanged()方法~

LOL !

2015年7月29日星期三

百度地图baidumapSDK入门之地图显示

摸索了半下午的baidumapSDK_v3.1.0的使用。

1.申请API KEY

地址http://lbsyun.baidu.com/apiconsole/key

填写安全码的时候注意下,其格式为SHA1+";"+包名,

在eclipse->preferences->android->build可找到SHAI fingerprint,

2.配置SDK

先下载之http://developer.baidu.com/map/index.php?title=androidsdk/sdkandev-download,建议一键下载,jdk和docs文档和sample都有了,想要啥有啥.....其实我想单选下载的时候老下载不成功

lib中的baidumapapi_v3_1_0.jar即jar文件放入工程的libs中,

libBaiduMapSDK_v3_1_0.so放入libs->armeabi中,注意,armeabi需要我们自己新建

Properties->Jaca Build Path->Libraies->add JARs 添加我们的baidumapapi_v3_1_0.jar

Properties->Jaca Build Path->Order and Export选中baidumapapi_v3_1_0.jar

Project clean -> clean all即可

3.在AndroidManifest.xml的application中添加API Key

   <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <meta-data 
            android:name="com.baidu.lbsapi.API_KEY"

            android:value="xzBRnhNM24BPN8bPBQVvaio2"/>
         .......
    </application>

添加所需权限

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_GPS" />

触目惊心好多权限.....

4.布局activity_main.xml文件里简单添加一个地图控件mapView,

  <com.baidu.mapapi.map.MapView
     android:id="@+id/map_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"

     android:clickable="true"/>

5.修改MainActivity.java文件

public class MainActivity extends Activity {

private MapView mapView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 在使用SDK各组件之前初始化context信息,传入ApplicationContext
// 注意该方法要在setContentView方法之前实现
// 注意,在SDK各功能组件使用之前都需要调用SDKInitializer.initialize(getApplicationContext())
// 因此我们建议该方法放在Application的初始化方法中
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.map_view);
}

@Override
protected void onResume() {
super.onResume();
// 在activity执行onResume时执行mapView.onResume(),实现地图生命周期管理
mapView.onResume();

}

@Override
protected void onPause() {
super.onPause();
// 在activity执行onPause时执行mapView.onPause(),实现地图生命周期管理
mapView.onPause();

}

@Override
protected void onDestroy() {
super.onDestroy();
// 在activity执行onDestroy时执行mapView.onDestroy(),实现地图生命周期管理
mapView.onDestroy();
}


}

详细的解释我都写在了代码注释里了,真是一个好习惯

6.运行之,


LOL !

eclipse中devices识别不了手机的adb server didn't ACK问题

经常会遇到电脑连上手机,eclipse中的devices空空如也的情况。娘希匹的,怎么又不识别了,老子的驱动明明安装正常,USB调试也明明打开了,反复开关几次eclipse他奶奶的还是不出来!到底是怎么回事儿,太影响享受APP开发调试成功的快感了——就像射精前后边有人给你踹了一脚立刻萎了精液回流了哪儿都疼尤其蛋疼一样。

我决心解决这个问题!

1.cmd开启控制台!

adb devices  启动adb服务



纳尼,ADB server didn't ACK??!!!

度娘说一般是端口绑定失败,那好,老子再来,adb nodaemon server,查看端口绑定信息,



connot bind 'tcp:5037',果真如此

那再看看是谁占用了5037端口好啦,netstat -ano | findstr "5037"



简直了!

打开任务管理器,查看PID,瞅瞅谁是



好尴尬,竟然是adb.exe,典型的站着茅坑不拉屎,关闭之!

再次运行adb devices,大力出奇迹啦!



重启eclipse,我的亲爱的抹茶回来啦!



总结!

1.遇到问题,首先重启adb.exe,像我这样就饶了一大圈
2.5037可能会被好多坏东东占用,包括豌豆荚91之流,tasklist /fi "pid eq ****" 也可以方便地查询
3.还没想到

LOL !




2015年7月28日星期二

LocationManager获取位置

1.首先,获取LocationManager的实例,

LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

2.选择一个位置提供器来确定设备当前的位置。

Android中一般有三种位置提供器,GPS_PROVIDER,NETWORK)PROVIDER和PASSIVE_PROVIDER。前两种使用的比较多,字面意思我们可以看出,分别是GPS定位和网络定位。

将选择好的位置提供其传入到getLastKnownLocation()方法中,就可以得到一个Location对象,其中包含了经度、纬度、海拔等一系列位置信息,如下,

String provider = LocationManager.NETWORK_PROVIDER;
Location location = locationManager.getLastKnownLocation(provider);

有时候我们的GPS开关没有打开,我们可以先判断一下有哪些位置提供其可以使用,

List<String> providerList = locationManager.getProviders(true);

getProvider()方法接受一布尔参数,传入true表示只有启用的位置提供器才会被返回,之后再从providerList中判断是哪个位置提供器即可。

3.requestLocationUpdates()更新位置

locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 500, 10, new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}

@Override
public void onProviderEnabled(String provider) {

}

@Override
public void onProviderDisabled(String provider) {

}

@Override
public void onLocationChanged(Location location) {
// 更新当前设备的位置信息
showLocation(location);
}
};

requestLocationUpdates接收到的四个参数分别是位置提供器类型,监听位置变化时间间隔(毫秒为单位),监听位置变化的距离间隔(米为单位),和LocationListener监听器。示例为每隔5s检测一下位置变化情况,当移动距离超过10m时,调用LocationListener的onLocationChanged()方法,并把新的位置信息作为参数传入。

4.最后,还是来一个鲜活的实例吧~~

MainActivity部分,

public class MainActivity extends Activity {

private TextView positionTextView;

private LocationManager locationManager;

private String provider;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
positionTextView = (TextView) findViewById(R.id.position_text_view);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// 获取所有可用的为之提供器
List<String> providerList = locationManager.getProviders(true);
if (providerList.contains(LocationManager.GPS_PROVIDER)) {
provider = LocationManager.GPS_PROVIDER;
} else if (providerList.contains(LocationManager.NETWORK_PROVIDER)) {
provider = locationManager.NETWORK_PROVIDER;
} else {
// 当没有可用的位置提供器时,弹出Toast提示用户
Toast.makeText(this, "No location provider to use",
Toast.LENGTH_LONG).show();
return;
}
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
// 显示当前设备的位置信息
showLocation(location);
}
locationManager.requestLocationUpdates(provider, 5000, 1,
locationListener);
}

protected void onDestroy() {
super.onDestroy();
if (locationManager != null) {
// 关闭程序时将监听器移除
locationManager.removeUpdates(locationListener);
}
}

LocationListener locationListener = new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}

@Override
public void onProviderEnabled(String provider) {

}

@Override
public void onProviderDisabled(String provider) {

}

@Override
public void onLocationChanged(Location location) {
// 更新当前设备的位置信息
showLocation(location);
}
};

private void showLocation(Location location) {
String currentPosition = "latitude is " + location.getLatitude() + "\n"
+ "longitude is " + location.getLongitude();
positionTextView.setText(currentPosition);
}
}

布局文件为一个简单的TextView,xml代码略去;

最后,记得在AndoidManifes.xml中声明权限,

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

运行程序,由于手机连接的wifi不能访问Internet(网络不正常),干脆关掉wifi,只打开GPS,过了打开半分钟,才蹦出经纬度信息,如下,


如关闭GPS和网络(即飞行模式),则会弹出Toast提示No location provider to use。

LOL!





2015年7月24日星期五

使用HTTP协议访问网络之HttpClient

HttpClient是Apache提供的HTTP网络访问接口,其可以完成与HttpURLConnection几乎(?)一模一样的效果。这次写一下HttpClient。

HttpClient是一个接口,因此无法创建它的实例,通常情况下会创建一个DefaultHttpClient的实例,如下:

HttpClient httpClient = new DefaultHttpClient();

接下来发起GET或POST请求,如下。

1.要发起GET请求,创建一个HttpGET对象,并传入目标的网络地址,然后调用HttpClient的execute()方法,

   HttpGet httpGet = new HttpGet("http://www.baidu.com");
   httpClient.execute(httpGet);

2.若要发起一条POST请求,需创建一个HttpPost对象,并传入目标的网络地址,

  HttpPost httpPost = new HttpPost("http://www.baidu.com");
  
   然后通过一个NameValuePair集合来存放待提交的参数,并将这个参数合并传入到一个          UrlEncodedFormEnity中,然后调用HttpPost的setEnity()方法将构建好的    UrlEncodedFormEnity传入,如下:

  List<NameValuePair> params = new ArrayList<NameValuePair>();
  params.add(new BasicNameValuePair("username", "admin"));
  params.add(new BasicNameValuePair("password", "123456"));
  UrlEncodedFormEnity entity = new UrlEncodedFormEnity(params, "utf-8");
  httpPost.setEnity(enity);
  httpClient.execute(httpPost);


执行execute()方法之后会返回一个HttpRespomse对象,服务器所返回的所有信息就会包含在这里面。通常情况下我们会先去除服务器返回的状态码,如果等于200就说明请求和相应都成功了,如下所示,

if (httpResponse.getStatusLine().getStatusCode() == 200) {
               //请求和响应成功啦
}

接下来再这个If判断的内部去除服务返回的具体内容,可以调用getEntity()方法获取到一个HttpEnity实例,然后再用EnityUtils.toString()这个静态方法将HttpEnity转换成字符串,

HttpEnity enity = httpResponse.getEnity();
String response = EnityUtils.toString(enity, "utf-8");//防止中文乱码


最后,来一个完整的例子,

private void senRequestWithHttpClient() {
new Thread(new Runnable() {
@Override
public void run() {
try {
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet ("http://www.baidu.com");
HttpResponse httpResponse = httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode() == 200) {
//请求和相应都成功了
HttpEnity enity = httpResponse.getEntity();
String response = EnityUtils.toString(entity, "utf-8");
Message message = new Message();
message.what = SHOW_RESPONSE;
//将服务器返回的结果存放在Message中
message.obj = response.toString();
handler.sendMessage(message);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}).start();
}


LOL!

使用HTTP协议访问网络之HttpURLConnection

Android中发送HTTP请求的方式一般有两种,HttpURLConnection和HttpClient,我分别实践了一下。

一、使用HttpURLConnection

首先需要获取到HttpURLConnection的实例——new出一个URL对象,传入目标的网络地址,调用openConnection()方法,如下:

URL url = new URL("http://www.baidu.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

得到HttpURLConnection实例后,设置HTTP请求所使用的方法,常用有两种,GET和POST。GET表示从服务器获取数据,POST相反,表示提交数据给服务器。

connection.setRequestMethod("GET"); //或POST

其他设置,

connection.setConnecTimeout(1000); //设置连接超时的毫秒数
connection.setReadTimeout(1000); //设置读取超时的毫秒数

调用getInputStream()方法获取服务器返回的输入流,并对输入流进行读取,

InputStream in = connection.getInputStream();

最后,调用disconnect()方法关闭此HTTP连接。

connection.disconnection();

如上是GET的简单用法,那么再来一个POST的完整例子吧。


//提交数据给服务器。获取输入流之前把药提交的数据写出即可
URL url = new URL("http://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST"); 
DataOutputStream out= new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&&&password=123456");//提交账号和密码

注意,要提交的数据要以键值对的形式存在,数据与数据之间用&隔开

LOL!

最后,来一个完整的例子,

private void sendRequestWithHttpUrlConnection() {
// 开启线程来发起网络请求 子线程
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL("http://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");// SET表示希望从服务器那里获取数据,POST表示希望提交数据给服务器
connection.setConnectTimeout(8000);// 设置连接超时
connection.setReadTimeout(8000);// 读取超时的毫秒数
InputStream in = connection.getInputStream();// 获取服务器返回的输入流
/*
* 提交数据给服务器。获取输入流之前把药提交的数据写出即可
* connection.setRequestMethod("POST"); 
* DataOutputStream out= new DataOutputStream(connection.getOutputStream());
* out.writeBytes("username=admin&&&password=123456");
* 数据以键值对形式存在
*/
// 下面对获取到的输入流进行读取
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
Message message = new Message();
message.what = SHOW_RESPONSE;
// 将服务器返回的结果存放到Message中
message.obj = response.toString();
handler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();// 将HTTP链接关闭掉
}
}
}
}).start();

}

LOL!

2015年7月22日星期三

Android服务之IntentService

服务中的代码默认运行在主线程当中,如果直接在服务里去处理一些耗时的逻辑,很容易出现ANR--即万恶的Application Not Responding。故此时需要用到Android多线程编程的技术,我们应该在服务的每个具体的方法开启一下子线程,然后再这里去处理那些耗时的逻辑,示例如下:

public class MyService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
            return null;
    }
    @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
             new Thread(new Runnable() {
                    @Override
                     public void run() {
                     // 处理具体的逻辑
                     }
              }).start();
              return super.onStartCommand(intent, flags, startId);
           }
 }

这种服务一旦启动,就会一直处于运行状态,必须调用stopService()或者stopService()方法才能使其停止,我们可以在run()中“//处理具体的逻辑”后调用 stopSelf()方法来实现服务执行完毕后的自动停止功能。

问题又来了,这样比较繁琐不好记,要么忘记开启线程,要么忘记调用stopService()方法,我们会经常性地忽视之。


为了可以简单地创建一个异步的、会自动停止的服务,IntentService闪亮登场啦。

一、新建MyIntentService类继承自IntentService,如下,

public class MyIntentService extends IntentService {

public MyIntentService() {
super("MyIntentService"); // 调用父类的有参构造函数
// TODO Auto-generated constructor stub
}

@Override
protected void onHandleIntent(Intent intent) {
// TODO Auto-generated method stub
// 打印当前线程的id
Log.d("MyIntentService", "Thread id is "
+ Thread.currentThread().getId());
}

@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}


}

可以看到,MyIntentService类分三个部分:

  1. 是一个无参数的构造函数,须在构造函数内部调用父类的有参构造函数。
  2. 实现onHandleIntent()抽象方法,其中处理具体逻辑,且不用担心ANR问题(因为这个方法已经是在子线程中运行的)。为证实,我们Log打印当前线程的id
  3. 重写了onDestroy()方法,其在服务运行结束时运行。 
二、新建Button响应启动MyIntentService服务,并且打印主线程的id,关键代码如下,


@Override
public void onClick(View v) {
switch (v.getId()) {
                .........
case R.id.start_intent_service:
// 打印主线程的id
Log.d("MainActivity", "Thread is " + Thread.currentThread().getId());
Intent intentService = new Intent(this, MyIntentService.class);
startService(intentService);
default:
break;
}


三、很重要一点,切莫忘记在AndroidManifest对我们的MyIntentService服务注册

四、运行。

   点击StartIntentService按钮,LogCat输出如下,
 

 可以看到,MyIntentService和MainActivity所在的线程id是不一样的;onDestroy()方法得到执行,说明MyIntentService服务运行技术后自动停止销毁啦。

这就是,集开启线程和自动停止于一身的,金光闪亮的IntentService。

LOL

2015年7月18日星期六

Service启动流程及生命周期

service启动流程
======== case 1. bind a service ============
TestAidl-Client: ------ =====> bindService()------
TestAidl-Client: ------ <===== bindService()------
TestAidl-Service: ------ service onCreate()------
TestAidl-Service: ------ service onBind()------ :onBind() is only called once, even we call bindService() multiple times, onBind() wont't be invoked.
TestAidl-Client: ------ onServiceConnected()------

======== case 2. unbind a service ============
TestAidl-Client: ------ =====> unbindService()------:if unbindService() is called with an un-bound service, exception will occur : java.lang.IllegalArgumentException: Service not registered
TestAidl-Client: ------ <===== unbindService()------:NOTE: onServiceDisconnected() won't be called due to unbindService(), 只有当servie异常退出时,系统才会调用onServiceDisconnected()
TestAidl-Service: ------ service onUnbind()------:onUnbind() is only called once when the last bound component unbind from the service, usually the service onDestroy() will be called afterwards..
【TestAidl-Service: ------ service onDestroy()------】

======== case 3. start a service ============ 
TestAidl-Client: ------ =====> startService()------
TestAidl-Client: ------ <===== startService()------
TestAidl-Service: ------ service onCreate()------
TestAidl-Client: ------ service onStartCommand()------:You should never call onStartCommand() directly.

======== case 4. stop a service ============ 
TestAidl-Client: ------ =====> stopService()------:stopService() can be called multiple times, nothing happened.
TestAidl-Client: ------ <===== stopService()------
【TestAidl-Service: ------ service onDestroy()------】
所以,在Service,只有onStart( )可被多次调用(通过多次startService调用),其他onCreate( ),onBind( ),onUnbind( ),onDestory( )在一个生命周期中只能被调用一次。

service的生命周期
1. A Started service will be destroyed once stopService() is called, no matter which component calls.
2. A bound service will be destroyed only when the last bound component calls the unbindService().
3. if a service is both started and bound, it will be destroyed when stopService() is called and no one is bound to the service at that time,除此之外,不管是调用unbindService()或者stopService()都是不起作用的。

2015年7月9日星期四

使用容器读取系统联系人

容器,即内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,其提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。

使用内容提供器是Android实现跨程序共享数据的标准方式,不同于文件存储和SharePreferences存储中的两种全局可读写操作模式,内容提供其可以选择只对哪一部分数据进行共享,从而保证了关键,隐私数据的安全。

下面使用现有的内容提供器来读取相应程序中的数据,使用容器来读取系统联系人。

1. 布局文件,使用ListView来显示读取出来的联系人信息

    <ListView 
        android:id="@+id/contacts_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        ></ListView>

2.MainActivity部分代码

public class MainActivity extends Activity {

ListView contactsView;

ArrayAdapter<String> adapter;

List<String> contactsList = new ArrayList<String>();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, contactsList);
contactsView.setAdapter(adapter);
readContacts();
}

private void readContacts() {
Cursor cursor = null;
try {
// 查询联系人数据
cursor = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, // 封装好的CONTENET_URI常量
null, null, null);
while (cursor.moveToNext()) {
// 获取联系人姓名
String displayName = cursor
.getString(cursor
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
// 获取联系人手机号
String number = cursor
.getString(cursor
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close(); // 最后将cursor对象关闭掉
}
}
}

....
}


LOL