标签归档:Android

Android中webview和js之间的交互

 

1.android中利用webview调用网页上的js代码。

Android 中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为true,,然后通过loadUrl就可以直接进行调用,如下所示:

mWebView.getSettings().setJavaScriptEnabled(true);

mWebView.loadUrl(“javascript:test()”);

2. 网页上调用android中java代码的方法

在网页中调用java代码,需要在webview控件中添加javascriptInterface。如下所示:

mWebView.addJavascriptInterface(new Object() {

            @JavascriptInterface

            public void clickOnAndroid() {

                mHandler.post(new Runnable() {

                    public void run() {

                        Toast.makeText(Test.this, “测试调用java”, Toast.LENGTH_LONG).show();

                    }

                });

            }

        }, “demo”);

需要特别注意的是,在 JELLY_BEAN_MR1 之后,只有 public 且添加了 @JavascriptInterface 注解的方法才能被调用。这也是为了安全考虑。毕竟页面可以直接操作 Native Application 有点太可怕了。(没加注解,导致反射无效,查了好久也是醉了)

在网页中,只需要像调用js方法一样,进行调用就可以

<div id=’b’><a onclick=”window.demo.clickOnAndroid()”>b.c</a></div>

3. Java代码调用js并传参

首先需要带参数的js函数,如function test(str),然后只需在调用js时传入参数即可,如下所示:

mWebView.loadUrl(“javascript:test(‘aa’)”);

4.Js中调用java函数并传参

首先一样需要带参数的函数形式,但需注意此处的参数需要final类型,即得到以后不可修改,如果需要修改其中的值,可以先设置中间变量,然后进行修改。如下所示:

mWebView.addJavascriptInterface(new Object() {

            @JavascriptInterface

            public void clickOnAndroid(final int i) {

                mHandler.post(new Runnable() {

                    public void run() {

                                int j = i;

                                j++;

Toast.makeText(Test.this, “测试调用java” + String.valueOf(j), Toast.LENGTH_LONG).show();

                    }

                });

            }

        }, “demo”);

然后在html页面中,利用如下代码<div id=’b’><a onclick=”window.demo.clickOnAndroid(2)”>b.c</a></div>,

即可实现调用。

IMPORTANT

  • Javascript 和 Java 的交互是在子线程中的,必须注意线程安全
  • Java 对象中的成员方法不再能直接访问
  • For applications targeted to API level LOLLIPOP and above, methods of injected Java objects are enumerable from JavaScript. (目前还不知道官方文档中的这句话代表了什么)
  • 特别需要注意 Android 各个版本对 WebView 的要求导致的。越高的版本对 WebView 的限制更加严格。

References

http://developer.android.com/reference/android/webkit/WebView.html

Android Service生命周期及用法!

Service概念及用途:

Android中的服务,它与Activity不同,它是不能与用户交互的,不能自己启动的,运行在后台的程序,如果我们退出应用时,Service进程并没有结束,它仍然在后台运行,那
我们什么时候会用到Service呢?比如我们播放音乐的时候,有可能想边听音乐边干些其他事情,当我们退出播放音乐的应用,如果不用Service,我
们就听不到歌了,所以这时候就得用到Service了,又比如当我们一个应用的数据是通过网络获取的,不同时间(一段时间)的数据是不同的这时候我们可以
用Service在后台定时更新,而不用每打开应用的时候在去获取。

Service生命周期
:

Android Service的生命周期并不像Activity那么复杂,它只继承了onCreate(),onStart(),onDestroy()三个方法,当我们第一次启动Service时,先后调用了onCreate(),onStart()这两个方法,当停止Service时,则执行onDestroy()方法,这里需要注意的是,如果Service已经启动了,当我们再次启动Service时,不会在执行onCreate()方法,而是直接执行onStart()方法,具体的可以看下面的实例。

Service与Activity通信:
Service后端的数据最终还是要呈现在前端Activity之上的,因为启动Service时,系统会重新开启一个新的进程,这就涉及到不同进程间通信的问题了(AIDL)这一节我不作过多描述,当我们想获取启动的Service实例时,我们可以用到bindService和onBindService方法,它们分别执行了Service中IBinder()和onUnbind()方法。

 

为了让大家 更容易理解,我写了一个简单的Demo,大家可以模仿着我,一步一步的来。

 

第一步:新建一个Android工程,我这里命名为ServiceDemo.

第二步:修改main.xml代码,我这里增加了四个按钮,代码如下:

 

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:orientation=“vertical”
  4.     android:layout_width=“fill_parent”
  5.     android:layout_height=“fill_parent”
  6.     >
  7.     <TextView
  8.         android:id=“@+id/text”
  9.         android:layout_width=“fill_parent”
  10.         android:layout_height=“wrap_content”
  11.         android:text=“@string/hello”
  12.         />
  13.     <Button
  14.         android:id=“@+id/startservice”
  15.         android:layout_width=“fill_parent”
  16.         android:layout_height=“wrap_content”
  17.         android:text=“startService”
  18.     />
  19.     <Button
  20.         android:id=“@+id/stopservice”
  21.         android:layout_width=“fill_parent”
  22.         android:layout_height=“wrap_content”
  23.         android:text=“stopService”
  24.     />
  25.     <Button
  26.         android:id=“@+id/bindservice”
  27.         android:layout_width=“fill_parent”
  28.         android:layout_height=“wrap_content”
  29.         android:text=“bindService”
  30.     />
  31.     <Button
  32.         android:id=“@+id/unbindservice”
  33.         android:layout_width=“fill_parent”
  34.         android:layout_height=“wrap_content”
  35.         android:text=“unbindService”
  36.     />
  37. </LinearLayout>

第三步:新建一个Service,命名为MyService.java代码如下:

 

  1. package com.tutor.servicedemo;
  2. import android.app.Service;
  3. import android.content.Intent;
  4. import android.os.Binder;
  5. import android.os.IBinder;
  6. import android.text.format.Time;
  7. import android.util.Log;
  8. public class MyService extends Service {
  9.     //定义个一个Tag标签
  10.     private static final String TAG = “MyService”;
  11.     //这里定义吧一个Binder类,用在onBind()有方法里,这样Activity那边可以获取到
  12.     private MyBinder mBinder = new MyBinder();
  13.     @Override
  14.     public IBinder onBind(Intent intent) {
  15.         Log.e(TAG, “start IBinder~~~”);
  16.         return mBinder;
  17.     }
  18.     @Override
  19.     public void onCreate() {
  20.         Log.e(TAG, “start onCreate~~~”);
  21.         super.onCreate();
  22.     }
  23.     @Override
  24.     public void onStart(Intent intent, int startId) {
  25.         Log.e(TAG, “start onStart~~~”);
  26.         super.onStart(intent, startId);
  27.     }
  28.     @Override
  29.     public void onDestroy() {
  30.         Log.e(TAG, “start onDestroy~~~”);
  31.         super.onDestroy();
  32.     }
  33.     @Override
  34.     public boolean onUnbind(Intent intent) {
  35.         Log.e(TAG, “start onUnbind~~~”);
  36.         return super.onUnbind(intent);
  37.     }
  38.     //这里我写了一个获取当前时间的函数,不过没有格式化就先这么着吧
  39.     public String getSystemTime(){
  40.         Time t = new Time();
  41.         t.setToNow();
  42.         return t.toString();
  43.     }
  44.     public class MyBinder extends Binder{
  45.         MyService getService()
  46.         {
  47.             return MyService.this;
  48.         }
  49.     }
  50. }

第四步:修改ServiceDemo.java,代码如下:

 

  1. package com.tutor.servicedemo;
  2. import android.app.Activity;
  3. import android.content.ComponentName;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.ServiceConnection;
  7. import android.os.Bundle;
  8. import android.os.IBinder;
  9. import android.view.View;
  10. import android.view.View.OnClickListener;
  11. import android.widget.Button;
  12. import android.widget.TextView;
  13. public class ServiceDemo extends Activity implements OnClickListener{
  14.     private MyService  mMyService;
  15.     private TextView mTextView;
  16.     private Button startServiceButton;
  17.     private Button stopServiceButton;
  18.     private Button bindServiceButton;
  19.     private Button unbindServiceButton;
  20.     private Context mContext;
  21.     //这里需要用到ServiceConnection在Context.bindService和context.unBindService()里用到
  22.     private ServiceConnection mServiceConnection = new ServiceConnection() {
  23.         //当我bindService时,让TextView显示MyService里getSystemTime()方法的返回值 
  24.         public void onServiceConnected(ComponentName name, IBinder service) {
  25.             // TODO Auto-generated method stub
  26.             mMyService = ((MyService.MyBinder)service).getService();
  27.             mTextView.setText(“I am frome Service :” + mMyService.getSystemTime());
  28.         }
  29.         public void onServiceDisconnected(ComponentName name) {
  30.             // TODO Auto-generated method stub
  31.         }
  32.     };
  33.     public void onCreate(Bundle savedInstanceState) {
  34.         super.onCreate(savedInstanceState);
  35.         setContentView(R.layout.main);
  36.         setupViews();
  37.     }
  38.     public void setupViews(){
  39.         mContext = ServiceDemo.this;
  40.         mTextView = (TextView)findViewById(R.id.text);
  41.         startServiceButton = (Button)findViewById(R.id.startservice);
  42.         stopServiceButton = (Button)findViewById(R.id.stopservice);
  43.         bindServiceButton = (Button)findViewById(R.id.bindservice);
  44.         unbindServiceButton = (Button)findViewById(R.id.unbindservice);
  45.         startServiceButton.setOnClickListener(this);
  46.         stopServiceButton.setOnClickListener(this);
  47.         bindServiceButton.setOnClickListener(this);
  48.         unbindServiceButton.setOnClickListener(this);
  49.     }
  50.     public void onClick(View v) {
  51.         // TODO Auto-generated method stub
  52.         if(v == startServiceButton){
  53.             Intent i  = new Intent();
  54.             i.setClass(ServiceDemo.this, MyService.class);
  55.             mContext.startService(i);
  56.         }else if(v == stopServiceButton){
  57.             Intent i  = new Intent();
  58.             i.setClass(ServiceDemo.this, MyService.class);
  59.             mContext.stopService(i);
  60.         }else if(v == bindServiceButton){
  61.             Intent i  = new Intent();
  62.             i.setClass(ServiceDemo.this, MyService.class);
  63.             mContext.bindService(i, mServiceConnection, BIND_AUTO_CREATE);
  64.         }else{
  65.             mContext.unbindService(mServiceConnection);
  66.         }
  67.     }
  68. }

第五步:修改AndroidManifest.xml代码(将我们新建的MyService注册进去如下代码第14行:)

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <manifest xmlns:android=“http://schemas.android.com/apk/res/android”
  3.       package=“com.tutor.servicedemo”
  4.       android:versionCode=“1”
  5.       android:versionName=“1.0”>
  6.     <application android:icon=“@drawable/icon” android:label=“@string/app_name”>
  7.         <activity android:name=“.ServiceDemo”
  8.                   android:label=“@string/app_name”>
  9.             <intent-filter>
  10.                 <action android:name=“android.intent.action.MAIN” />
  11.                 <category android:name=“android.intent.category.LAUNCHER” />
  12.             </intent-filter>
  13.         </activity>
  14.         <service android:name=“.MyService” android:exported=“true”></service>
  15.     </application>
  16.     <uses-sdk android:minSdkVersion=“7” />
  17. </manifest>

 

第六步:执行上述工程,效果图如下:

点击startServie按钮时先后执行了Service中onCreate()->onStart()这两个方法,打开Logcat视窗效果如下图:

我们这时可以按HOME键进入Settings(设置)->Applications(应用)->Running Services(正在运行的服务)看一下我们新启动了一个服务,效果如下:

点击stopService按钮时,Service则执行了onDestroy()方法,效果图如下所示:

这时候我们再次点击startService按钮,然后点击bindService按钮(通常bindService都是bind已经启动的Service),我们看一下Service执行了IBinder()方法,以及TextView的值也有所变化了,如下两张图所示:

 

最后点击unbindService按钮,则Service执行了onUnbind()方法,如下图所示:

 

Android中定时执行任务的3种实现方法

在Android开发中,定时执行任务的4种实现方法:

  • 采用Handler与线程的sleep(long)方法(不建议使用,java的实现方式)
  • 采用Handler的postDelayed(Runnable, long)方法(最简单的android实现)
  • 采用Handler与timer及TimerTask结合的方法(比较多的任务时建议使用)
  • AlarmManager与PendingIntent

下面逐一介绍:

一、采用Handle与线程的sleep(long)方法

Handler主要用来处理接受到的消息。这只是最主要的方法,当然Handler里还有其他的方法供实现,有兴趣的可以去查API,这里不过多解释。

  1. 定义一个Handler类,用于处理接受到的Message。
Handler handler = new Handler() {  
    public void handleMessage(Message msg) {  
        // 要做的事情  
        super.handleMessage(msg);  
    }  
};  

2.新建一个实现Runnable接口的线程类,如下:

public class MyThread implements Runnable {  
    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        while (true) {  
            try {  
                Thread.sleep(10000);// 线程暂停10秒,单位毫秒  
                Message message = new Message();  
                message.what = 1;  
                handler.sendMessage(message);// 发送消息  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
    }  
}  

3.在需要启动线程的地方加入下面语句:

new Thread(new MyThread()).start();  

4.启动线程后,线程每10s发送一次消息。

二、采用Handler的postDelayed(Runnable, long)方法

这个实现比较简单一些。

  1. 定义一个Handler类
Handler handler=new Handler();  
Runnable runnable=new Runnable() {  
    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        //要做的事情  
        handler.postDelayed(this, 2000);  
    }  
};  

2.启动计时器

handler.postDelayed(runnable, 2000);//每两秒执行一次runnable.  

3.停止计时器

handler.removeCallbacks(runnable);   

三、采用Handler与timer及TimerTask结合的方法

1.定义定时器、定时器任务及Handler句柄

private final Timer timer = new Timer();  
private TimerTask task;  
Handler handler = new Handler() {  
    @Override  
    public void handleMessage(Message msg) {  
        // TODO Auto-generated method stub  
        // 要做的事情  
        super.handleMessage(msg);  
    }  
};  

2.初始化计时器任务

task = new TimerTask() {  
    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        Message message = new Message();  
        message.what = 1;  
        handler.sendMessage(message);  
    }  
};   

3.启动定时器

timer.schedule(task, 2000, 2000);   

4.停止计时器

timer.cancel();  

四、AlarmManager与PendingIntent

AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = createAlarmIntent();//启动自身服务的Intent
long nextTime = SystemClock.elapsedRealtime() + NEXT_DELAY;
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, pi);

简要说一下上面三步提到的一些内容:

  1. 定时器任务(TimerTask)顾名思义,就是说当定时器到达指定的时间时要做的工作,这里是想Handler发送一个消息,由Handler类进行处理。
  2. java.util.Timer.schedule(TimerTask task, long delay):这个方法是说,dalay/1000秒后执行task.只执行一次。
    java.util.Timer.schedule(TimerTask task, long delay, long period):这个方法是说,delay/1000秒后执行task,然后进过period/1000秒再次执行task,这个用于循环任务,执行无数次,当然,你可以用timer.cancel();取消计时器的执行。

每一个Timer仅对应唯一一个线程。
Timer不保证任务执行的十分精确。
Timer类的线程安全的。

 

Android – 线程同步

什么是线程同步?

    当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。

实现同步机制有两个方法:
1、同步代码块:
synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。

 

2、同步方法:
public synchronized 数据返回类型方法名(){}

 

通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:
1
,该类的对象可以被多个线程安全的访问。
2
,每个线程调用该对象的任意方法之后,都将得到正确的结果。
3
,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等

 

※不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。

线程通讯:

    当使用synchronized 来修饰某个共享资源时(分同步代码块和同步方法两种情况),当某个线程获得共享资源的锁后就可以执行相应的代码段,直到该线程运行完该代码段后才释放对该共享资源的锁,让其他线程有机会执行对该共享资源的修改。当某个线程占有某个共享资源的锁时,如果另外一个线程也想获得这把锁运行就需要使用wait()
和notify()/notifyAll()方法来进行线程通讯了。
Java.lang.object 里的三个方法wait() notify()  notifyAll()


wait()

导致当前线程等待,直到其他线程调用同步监视器的notify方法或notifyAll方法来唤醒该线程。

wait(mills)

都是等待指定时间后自动苏醒,调用wait方法的当前线程会释放该同步监视器的锁定,可以不用notify或notifyAll方法把它唤醒。

notify()
唤醒在同步监视器上等待的单个线程,如果所有线程都在同步监视器上等待,则会选择唤醒其中一个线程,选择是任意性的,只有当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

notifyAll()

唤醒在同步监视器上等待的所有的线程。只用当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

 

 

==================================================================================================

 

 

原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括longdouble, 因为JVM看到的基本存储单位是32位,而long double都要用64位来表示。所以无法在一个时钟周期内完成。 

自增操作(++)不是原子操作,因为它涉及到一次读和一次写。 

原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。 

同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。 

同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。 

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。 
1
 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。 
2
 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。 (一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁 如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中 


线程同步的特征: 
1
 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制) 
2
 每个对象都有唯一的同步锁 
3
 在静态方法前面可以使用synchronized修饰符。 
4
 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。 
5
 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。 

释放对象的锁: 
1
 执行完同步代码块就会释放对象的锁 
2
 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放 
3
 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。 

死锁:

线程1独占(锁定)资源A,等待获得资源B后,才能继续执行
同时
线程2独占(锁定)资源B,等待获得资源A后,才能继续执行
这样就会发生死锁,程序无法正常执行

 

如何避免死锁 
一个通用的经验法则是:当几个线程都要访问共享资源ABC 时,保证每个线程都按照同样的顺序去访问他们。

 

 

==================================================================================================

注意:

1、线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。

2、只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。
3、只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。
4、多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

同步锁:

    我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。

同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。

访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。
这就是说,
同步锁本身也一定是多个线程之间的共享对象。

public static final Object lock1 = new Object();

… f1() {

synchronized(lock1){ // lock1 是公用同步锁
  // 代码段 A
// 访问共享资源 resource1
// 需要同步
}

你不一定要把同步锁声明为static或者public,但是你一定要保证相关的同步代码之间,一定要使用同一个同步锁。
任何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。因此,要保证同步代码段之间使用的是同一个同步锁,我们就要保证这些同步代码段的synchronized关键字使用的是同一个Object Reference,同一个内存地址。这也是为什么我在前面的代码中声明lock1的时候,使用了final关键字,这就是为了保证lock1的Object
Reference在整个系统运行过程中都保持不变。

竞争同步锁失败的线程进入的是该同步锁的就绪(Ready)队列,而不是后面要讲述的待召队列(Waiting Queue,也可以翻译为等待队列)。就绪队列里面的线程总是时刻准备着竞争同步锁,时刻准备着运行。而待召队列里面的线程则只能一直等待,直到等到某个信号的通知之后,才能够转移到就绪队列中,准备运行。

 

同步粒度
在Java语言里面,我们可以直接把synchronized关键字直接加在函数的定义上。
比如。
… synchronized … f1() {
 // f1 代码段
}

这段代码就等价于
… f1() {
 synchronized(this){ // 同步锁就是对象本身
   // f1 代码段
 }
}

同样的原则适用于静态(static)函数
比如。
… static synchronized … f1() {
 // f1 代码段
}

这段代码就等价于
…static … f1() {
 synchronized(Class.forName(…)){ // 同步锁是类定义本身
   // f1 代码段
  }
}

但是,我们要尽量避免这种直接把synchronized加在函数定义上的偷懒做法。因为我们要控制同步粒度。同步的代码段越小越好。synchronized控制的范围越小越好。

 


 

===============================附:JAVA开发实战经典 源码===============================

package com.synchronization;
class Info{ // 定义信息类
 private String name = “李兴华”;  // 定义name属性
 private String content = “JAVA讲师”  ;  // 定义content属性
 private boolean flag = false ; // 设置标志位
 
public synchronized void set(String name,String content){
 if(!flag){
  try{
   super.wait() ;
  }catch(InterruptedException e){
   e.printStackTrace() ;
  }
 }
 this.setName(name) ; // 设置名称
  try{
  Thread.sleep(300) ;
 }catch(InterruptedException e){
  e.printStackTrace() ;
 }
 this.setContent(content) ; // 设置内容
  flag  = false ; // 改变标志位,表示可以取走
  super.notify() ;
}

public synchronized void get(){
 if(flag){
  try{
   super.wait() ;
  }catch(InterruptedException e){
   e.printStackTrace() ;
  }
 }
 try{
  Thread.sleep(300) ;
 }catch(InterruptedException e){
  e.printStackTrace() ;
 }
 System.out.println(this.getName() +
  ” –> ” + this.getContent()) ;
 flag  = true ; // 改变标志位,表示可以生产
  super.notify() ;
}
public void setName(String name){
 this.name = name ;
}
public void setContent(String content){
 this.content = content ;
}
public String getName(){
 return this.name ;
}
public String getContent(){
 return this.content ;
}
}

class Producer implements Runnable{ // 通过Runnable实现多线程
 private Info info = null ;  // 保存Info引用
 public Producer(Info info){
 this.info = info ;
}
public void run(){
 boolean flag = false ; // 定义标记位
  for(int i=0;i<50;i++){
  if(flag){
   this.info.set(“李兴华”,”JAVA讲师”) ; // 设置名称
    flag = false ;
  }else{
   this.info.set(“mldn”,”
www.mldnjava.cn“)
// 设置名称
    flag = true ;
  }
 }
}
}

class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
 this.info = info ;
}
public void run(){
 for(int i=0;i<50;i++){
  this.info.get() ;
 }
}
}

public class ThreadCaseDemo{
public static void main(String args[]){
 Info info = new Info(); // 实例化Info对象
  Producer pro = new Producer(info) ; // 生产者
  Consumer con = new Consumer(info) ; // 消费者
  new Thread(pro).start() ;
 new Thread(con).start() ;
}
}

 

android declare-styleable中format 参考说明

我们在做项目的时候,由于Android自带的属性不能满足需求,android提供了自定义属性的方法,其中的format是做什么用的?以及如何使用它?下面列出一些常用的。

  • reference:参考某一资源ID。
  • color:颜色值。
  • boolean:布尔值。
  • dimension:尺寸值。
  • float:浮点值。
  • integer:整型值。
  • string:字符串。
  • fraction:百分数。
  • enum:枚举值。
  • flag:位或运算。

具体示例如下:

  • reference:参考某一资源ID。
    1. 属性定义:
    <declare-styleable name = "名称">
        <attr name = "background" format = "reference" />
    </declare-styleable>
    

    2.属性使用:

    <ImageView
        android:layout_width = "42dip"
        android:layout_height = "42dip"
        android:background = "@drawable/图片ID"
    />
    
  • color:颜色值。
    1. 属性定义:
    <declare-styleable name = "名称">
        <attr name = "textColor" format = "color" />
    </declare-styleable>
    

    2.属性使用:

    <TextView
        android:layout_width = "42dip"
        android:layout_height = "42dip"
        android:textColor = "#00FF00"
    />
    
  • boolean:布尔值。
    1. 属性定义:
    <declare-styleable name = "名称">
         <attr name = "focusable" format = "boolean" />
    </declare-styleable>
    

    2. 属性使用:

      <Button
      android:layout_width = "42dip"
      android:layout_height = "42dip"
      android:focusable = "true"
      />
    
  • dimension:尺寸值。
    1. 属性定义:
    <declare-styleable name = "名称">
        <attr name = "layout_width" format = "dimension" />
    </declare-styleable>
    

    2.属性使用:

    <Button
            android:layout_width = "42dip"
            android:layout_height = "42dip"
    />
    
  • float:浮点值。
    1. 属性定义:
    <declare-styleable name = "AlphaAnimation">
        <attr name = "fromAlpha" format = "float" />
        <attr name = "toAlpha" format = "float" />
    </declare-styleable>
    

    2.属性使用:

    <alpha 
        android:fromAlpha = "1.0"
        android:toAlpha = "0.7"
    />
    
  • integer:整型值。
    1. 属性定义:
    <declare-styleable name = "AnimatedRotateDrawable">
        <attr name = "visible" />
        <attr name = "frameDuration" format="integer" />
        <attr name = "framesCount" format="integer" />
        <attr name = "pivotX" />
        <attr name = "pivotY" />
        <attr name = "drawable" />
    </declare-styleable>
    

    2.属性使用:

    <animated-rotate
        xmlns:android = "http://schemas.android.com/apk/res/android" 
         android:drawable = "@drawable/图片ID" 
         android:pivotX = "50%" 
         android:pivotY = "50%" 
         android:framesCount = "12" 
         android:frameDuration = "100"
      />
    

     

  • string:字符串。
    1. 属性定义:
      <declare-styleable name = "MapView">
            <attr name = "apiKey" format = "string" />
      </declare-styleable>
    

    2.属性使用:

    <com.google.android.maps.MapView
            android:layout_width = "fill_parent"
         android:layout_height = "fill_parent"
         android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g"
     />
    
  • fraction:百分数。
    1. 属性定义:
      <declare-styleable name="RotateDrawable">
            <attr name = "visible" />
            <attr name = "fromDegrees" format = "float" />
            <attr name = "toDegrees" format = "float" />        <attr name = "pivotX" format = "fraction" />
            <attr name = "pivotY" format = "fraction" />
         <attr name = "drawable" />
      </declare-styleable>
    

    2.属性使用:

    <rotate
      xmlns:android = "http://schemas.android.com/apk/res/android"
        android:interpolator = "@anim/动画ID"
        android:fromDegrees = "0"
        android:toDegrees = "360"
        android:pivotX = "200%"
        android:pivotY = "300%"
        android:duration = "5000"
        android:repeatMode = "restart"
        android:repeatCount = "infinite"
        />
    
  • enum:枚举值。
    1. 属性定义:
    <declare-styleable name="名称">
        <attr name="orientation">
        <enum name="horizontal" value="0" />
        <enum name="vertical" value="1" />
        </attr>           
    </declare-styleable>
    

    2.属性使用:

    <LinearLayout
        xmlns:android = "http://schemas.android.com/apk/res/android"
        android:orientation = "vertical"
        android:layout_width = "fill_parent"
        android:layout_height = "fill_parent"
        >
    </LinearLayout>
    
  • flag:位或运算。
    1. 属性定义:
    <declare-styleable name="名称">
         <attr name="windowSoftInputMode">
         <flag name = "stateUnspecified" value = "0" />
         <flag name = "stateUnchanged" value = "1" />
         <flag name = "stateHidden" value = "2" />
         <flag name = "stateAlwaysHidden" value = "3" />
         <flag name = "stateVisible" value = "4" />
         <flag name = "stateAlwaysVisible" value = "5" />
         <flag name = "adjustUnspecified" value = "0x00" />
         <flag name = "adjustResize" value = "0x10" />
         <flag name = "adjustPan" value = "0x20" />
         <flag name = "adjustNothing" value = "0x30" />
         </attr>        
    </declare-styleable>
    

    2.属性使用:

    <activity
        android:name = ".StyleAndThemeActivity"
        android:label = "@string/app_name"
        android:windowSoftInputMode = "stateUnspecified | stateUnchanged | stateHidden">
        <intent-filter>
            <action android:name = "android.intent.action.MAIN" />
            <category android:name = "android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    

特别要注意:属性定义时可以指定多种类型值。
1. 属性定义:

<declare-styleable name = "名称">
    <attr name = "background" format = "reference|color" />
</declare-styleable>

2.属性使用:

<ImageView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:background = "@drawable/图片ID|#00FF00"
/>

 

 

java.util.ConcurrentModificationException异常(转)

 

案例

今天在写一个带缓存功能的访问代理程序时出现了java.util.ConcurrentModificationException异常,
因为该异常是非捕获型异常而且很少见,所以费了些时间才找到问题所在,原来在通过Iterator进行遍历的时候,如果直接对HashMap进行操作后,再继续用之前的Iterator进行遍历就会出现这个异常,表示其HashMap已经被修改。

源程序代码片段如下:caches为一个HashMap对象

String sameKeyPart = accesserClassName + "@" + methodName + "@" +
parameterKeyString + "@";
Iterator keys = caches.keySet().iterator();
String key = null;
while (keys.hasNext()){
    key = (String) keys.next();     
    if (key.startsWith(sameKeyPart)){
        caches.remove(key);  
    }
}

解决办法为通过其相应的Iterator进行删除就可以了,修改后代码片段如下:

String sameKeyPart = accesserClassName + "@" + methodName + "@" +
parameterKeyString + "@";
Iterator keys = caches.keySet().iterator();
String key = null;
while (keys.hasNext()) {
    key = (String) keys.next();     
    if (key.startsWith(sameKeyPart)) {       
        keys.remove();
    }
}

多线程操作时解决办法

撰写多线程代码时,你遇到过多少次下面的提示:
Exception in thread “main” java.util.ConcurrentModificationException

这个异常产生的原因有几个。

  1. 直接对集合调用删除操作而不是在枚举器上。
  2. 不同的线程试图对集合进行增删操作的时候。

这个解决办法的第一步就是同步代码,使得你在枚举的时候其它的线程不能增删记录。但是如果每个枚举过程要进行复杂的计算或者是数据库访问的一部分的话,这个同步就会导致可怕的后果。为了减少负面影响,可以拷贝一个只读的枚举器,去掉同步,然后采用下列代码所示的方法:

private List list;
public void add(Object obj) {
    synchronized(list) {
        list.add(obj);
  }
}

public void perform( ) {
    Iterator iterator = null;
    synchronized(list) {
        iterator = new CopiedIterator(list.iterator());
    }
    while(iterator.hasNext( )) {
    // perform resource or cpu hungry work
    }
 }

重要的是记住,CopiedIterator不是一个克隆,只是一个只读的拷贝,所以它并没有保持原有的全部功能。最重要的是,不能再调用CopiedIterator.remove方法了。CopiedIterator.remove的实现如下:

public class CopiedIterator implements Iterator {
    private Iterator iterator = null;
    public CopiedIterator(Iterator itr) {
        LinkedList list = new LinkedList();
        while(itr.hasNext()) {
            list.add(itr.next());
        }
        this.iterator = list.iterator();
  }
  
  public boolean hasNext() {
        return this.iterator.hasNext();
  }
  
  public void remove() {
        throw new UnsupportedOperationException("This is a read-only iterator.");
  }
  
  public Object next() {
        return this.iterator.next();
  }
}

枚举器的只读拷贝将用在同步状态上的时间减少到最小,因此可以增强全局的效率。

单线程也有这种异常

当使用 fail-fast iterator 对 Collection 或 Map 进行迭代操作过程中尝试直接修改 Collection / Map 的内容时,即使是在单线程下运行, java.util.ConcurrentModificationException 异常也将被抛出。
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。
Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照
fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException 异常。
所以Iterator在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator本身的方法 remove() 来删除对象, Iterator.remove()方法会在删除当前迭代对象的同时维护索引的一致性。
有意思的是如果你的 Collection / Map对象实际只有一个元素的时候, ConcurrentModificationException异常并不会被抛出。这也就是为什么在 javadoc 里面指出: it would be wrong to write a program that depended on this exception for its
correctness: ConcurrentModificationException should be used only to
detect bugs.

import java.util.*;   
public final class MyTest   
{   
    private static HashMap p_mapList = new HashMap(2);
    private MyTest(){}   
    public static void init(){   
    // If only there are more than one element in Map,    
    // the ConcurrentModificationException will not be thrown.   
        p_mapList.put(new String("hello"),new String("world"));   
        p_mapList.put(new String("goto"),new String("hell"));   
    }   
    
    public static void clear() throws Exception{   
        Iterator pTmpKeys = null;   
        Long pTmpKeyLong;       
        pTmpKeys = p_mapList.keySet().iterator();   
        String pCurKey = null;   
        String pCurObj = null;   
        while(pTmpKeys.hasNext()){              
            pCurKey = (String)
            pTmpKeys.next();          
            pCurObj = (String)
            p_mapList.get(pCurKey);          
            p_mapList.put(pCurKey,null);             
            // You can not remove element in Map object directly.              
            //p_mapList.remove(pCurKey);              
            // But you can remove current element by iterator itself.               
            pTmpKeys.remove();               
            System.out.println(pCurKey + "removed.");   
     }         
        System.out.println(p_mapList.size() + " entries left after iterator.");   
        pTmpKeys = null;   
    }   
    
    public static void main(String[] args) throws Exception{
        MyTest.init();      
        MyTest.clear();   
    }   
}  

Android中的Selector的用法

Android中的Selector主要是用来改变ListView和Button控件的默认背景。其使用方法可以按一下步骤来设计:(以在mylist_view.xml为例)

  1. 创建mylist_view.xml文件

首先在res目录下新建drawable文件夹,再在新建的drawable文件夹中新建mylist_view.xml,其目录结构为:res/drawable/mylist_view.xml。

2.根据具体需求编辑mylist_view.xml文件
新建mylist_view.xml文件后,在没有添加任何属性时其内部代码结构为:

<?xml version="1.0" encoding="utf-8" ?>     
<selector xmlns:android="http://schemas.android.com/apk/res/android">   
   
</selector>  

下面就可以根据项目需求,在其内部定义为自己想要的样式了,主要属性如下:

<?xml version="1.0" encoding="utf-8" ?>     
<selector xmlns:android="http://schemas.android.com/apk/res/android">   
<!-- 默认时的背景图片-->    
  <item android:drawable="@drawable/pic1" />      
<!-- 没有焦点时的背景图片 -->    
  <item android:state_window_focused="false"     
        android:drawable="@drawable/pic1" />     
<!-- 非触摸模式下获得焦点并单击时的背景图片 -->    
  <item android:state_focused="true" android:state_pressed="true"   android:drawable= "@drawable/pic2" />   
<!-- 触摸模式下单击时的背景图片-->    
<item android:state_focused="false" android:state_pressed="true"   android:drawable="@drawable/pic3" />    
<!--选中时的图片背景-->    
  <item android:state_selected="true"   android:drawable="@drawable/pic4" />     
<!--获得焦点时的图片背景-->    
  <item android:state_focused="true"   android:drawable="@drawable/pic5" />     
</selector>  

3.引用mylist_view.xml文件
三种方法可以来引用刚才创建的文件:
1.在ListView中添加如下属性代码

android:listSelector="@drawable/mylist_view"  

2.在ListView的item界面中添加如下属性代码

android:background="@drawable/mylist_view"  

3.利用JAVA代码直接编写

Drawable drawable =     getResources().getDrawable(R.drawable.mylist_view);   
listView.setSelector(drawable);  

为了防止列表拉黑的情况发生,需要在ListView中添加以下的属性代码

android:cacheColorHint="@android:color/transparent"  

属性介绍:

  • android:state_selected选中
  • android:state_focused获得焦点
  • android:state_pressed点击
  • android:state_enabled设置是否响应事件,指所有事件

Android开发示例:如何设置圆角的EditText

设置EditText边框为圆角如下图所示:

首先定义一个圆角xml,路径:res/drawable/rounded_edittext.xml

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <shape xmlns:Android=“http://schemas.android.com/apk/res/android”
  3.     android:shape=“rectangle” android:padding=“10dp”>
  4.     <solid android:color=“#FFFFFF” />
  5.     <corners android:bottomRightRadius=“15dp”
  6.         android:bottomLeftRadius=“15dp” android:topLeftRadius=“15dp”
  7.         android:topRightRadius=“15dp” />
  8. </shape>

显示控件的xml中

  1. <EditText android:id=“@+id/edt_operator_name” style=“@style/syncEditText”
  2.                     android:hint=“@string/hint_operator_name”
  3. android:background=“@drawable/rounded_edittext”
  4. android:layout_width=“250dip”
  5. android:layout_height=“wrap_content”
  6. android:paddingTop=“10dip”
  7. android:paddingBottom=“10dip”
  8. android:gravity=“center_vertical” />

其中关键的就是设置编辑文本控件的background属性为刚定义的圆角xml。

即:

  1. android:background=“@drawable/rounded_edittext”

 

android软键盘弹出引起的各种不适终极解决方案

很多写登录界面的开发者都会遇到一个问题:那就是在登录界面时,当你点击输入框时,下边的按钮有时会被输入框挡住,这个不利于用户的体验,所以很多人希望软键盘弹出时,也能把按钮挤上去。很多开发者想要监听键盘的状态,这无疑是一个很麻烦的做法。
我们可以在AndroidManifest.xml的Activity设置属性:

xml
android:windowSoftInputMode = "adjustResize"

软键盘弹出时,要对主窗口布局重新进行布局,并调用onSizeChanged方法,切记一点当我们设置为“adjustResize”时,我们的界面不要设置为全屏模式,否则设置了这个属性也不会有什么效果。而当我们设置
xml
android: windowSoftInputMode = "adjustPan"

时,主窗口就不会调用onSizeChanged方法,界面的一部分就会被软键盘覆盖住,就不会被挤到软键盘之上了。

我们通过一段代码来测试一下,当我们设置了该属性后,弹出输入法时,系统做了什么.
重写Layout布局:

    public class ResizeLayout extends LinearLayout{ 
        private static int count = 0; 
         
        public ResizeLayout(Context context, AttributeSet attrs)            { 
            super(context, attrs); 
        } 
         
        @Override 
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {     
            super.onSizeChanged(w, h, oldw, oldh);          
            Log.e("onSizeChanged " + count++, "=>onResize called! w="+w + ",h="+h+",oldw="+oldw+",oldh="+oldh); 
       } 
         
        @Override 
        protected void onLayout(boolean changed, int l, int t, int r, int b) { 
            super.onLayout(changed, l, t, r, b); 
            Log.e("onLayout " + count++, "=>OnLayout called! l=" + l + ", t=" + t + ",r=" + r + ",b="+b); 
        } 
         
        @Override 
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);       
            Log.e("onMeasure " + count++, "=>onMeasure called! widthMeasureSpec=" + widthMeasureSpec + ", heightMeasureSpec=" + heightMeasureSpec); 
        } 

我们的布局设置为:

    <com.winuxxan.inputMethodTest.ResizeLayout  
        xmlns:android="http://schemas.android.com/apk/res/android" 
        android:id="@+id/root_layout" 
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
        android:orientation="vertical" 
        > 
         
        <EditText 
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
        /> 
       
        <LinearLayout 
                android:id="@+id/bottom_layout" 
                android:layout_width="fill_parent"  
                android:layout_height="fill_parent"  
                android:orientation="vertical" 
                android:gravity="bottom">s 
        
        <TextView   
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:text="@string/hello" 
            android:background="#77777777" 
          /> 
       </LinearLayout> 
    </com.winuxxan.inputMethodTest.ResizeLayout> 

AndroidManifest.xml的Activity设置属性:

android:windowSoftInputMode = "adjustResize"

运行程序,点击文本框,查看调试信息:

E/onMeasure 6(7960): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec = 1073742024

E/onMeasure 7(7960): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec = 1073742025

E/onSizeChanged 8(7960): =>onSizeChanged called! w=320,h=201,oldw=320,oldh=377

E/onLayout 9(7960): =>OnLayout called! l=0, t=0,r=320,b=201

从调试结果我们可以看出,当我们点击文本框后,根布局调用了onMeasure,onSizeChanged和onLayout。

windowSoftInputMode的值如果设置为adjustPan,那么该Activity主窗口并不调整屏幕的大小以便留出软键盘的空间。相反,当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分。这个通常是不期望比调整大小,因为用户可能关闭软键盘以便获得与被覆盖内容的交互操作。
上面的例子中,我们将AndroidManifest.xml的属性进行更改:

xml
android: windowSoftInputMode = "adjustPan"

重新运行,并点击文本框,查看调试信息:
E/onMeasure 6(8378): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec=1073742200
E/onMeasure 7(8378): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec=1073742201
E/onLayout 8(8378): =>OnLayout called! l=0, t=0,r=320,b=377
我们看到:系统也重新进行了measrue和layout,但是我们发现,layout过程中onSizeChanged并没有调用,这说明输入法弹出前后并没有改变原有布局的大小。

当然还有其他属性可以设置:

  • stateUnspecified 软键盘的状态(是否它是隐藏或可见)没有被指定。系统将选择一个合适的状态或依赖于主题的设置。 这个是为了软件盘行为默认的设置。
  • stateUnchanged 软键盘被保持无论它上次是什么状态,是否可见或隐藏,当主窗口出现在前面时。
  • stateHidden 当用户选择该Activity时,软键盘被隐藏——也就是,当用户确定导航到该Activity时,而不是返回到它由于离开另一个Activity。
  • stateAlwaysHidden 软键盘总是被隐藏的,当该Activity主窗口获取焦点时。
  • stateVisible 软键盘是可见的,当那个是正常合适的时(当用户导航到Activity主窗口时)。
  • stateAlwaysVisible 当用户选择这个Activity时,软键盘是可见的——也就是,也就是,当用户确定导航到该Activity时,而不是返回到它由于离开另一个Activity。
  • adjustUnspecifie 它不被指定是否该Activity主窗口调整大小以便留出软键盘的空间,或是否窗口上的内容得到屏幕上当前的焦点是可见的。系统将自动选择这些模式中一种主要依赖于是否窗口的内容有任何布局视图能够滚动他们的内容。如果有这样的一个视图,这个窗口将调整大小,这样的假设可以使滚动窗口的内容在一个较小的区域中可见的。这个是主窗口默认的行为设置。
  • adjustResize 该Activity主窗口总是被调整屏幕的大小以便留出软键盘的空间
  • adjustPan 该Activity主窗口并不调整屏幕的大小以便留出软键盘的空间。相反,当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分。这个通常是不期望比调整大小,因为用户可能关闭软键盘以便获得与被覆盖内容的交互操作。

 

Android 数据存储的5种方式介绍

5种方式分别是:

  • 文件
  • sharedPreference
  • SQLite
  • 网络
  • ContentProvider

Preference,File, DataBase这三种方式分别对应的目录是/data/data/Package Name/Shared_Pref, /data/data/Package Name/files, /data/data/Package Name/database 。
在Android中通常使用File存储方式是用Context.openFileOutput(String fileName, int mode)和Context.openFileInput(String fileName)。
Context.openFileOutput(String fileName, int mode)生成的文件自动存储在/data/data/Package Name/files目录下,其全路径是/data/data/Package Name/files/fileName 。注意下,这里的参数fileName不可以包含路径分割符(如"/")。

通常来说,这种方式生成的文件只能在这个apk内访问。但这个结论是指使用Context.openFileInput(String fileName)的方式。使用这种方式,每个apk只可以访问自己的/data/data/Package Name/files目录下的文件,原因很简单,参数fileName中不可以包含路径分割符,Android会自动在/data/data/Package Name/files目录下寻找文件名为fileName的文件。

  1. 使用SharedPreferences存储数据
    首先说明SharedPreferences存储方式,它是Android提供的用来存储一些简单配置信息的一种机制,例如:登录用户的用户名与密码。其采用了Map数据结构来存储数据,以键值的方式存储,可以简单的读取与写入,具体实例如下:
void  ReadSharedPreferences(){
    String  strName,strPassword;
    SharedPreferences   user = getSharedPreferences("user_info",0);
    strName = user.getString("NAME","");
    strPassword = user getString("PASSWORD","");
}
void  WriteSharedPreferences(String  strName,String strPassword){
    SharedPreferences   user = getSharedPreferences(“user_info”,0);
    user.edit();
    user.putString("NAME", strName);
    user.putString("PASSWORD" ,strPassword);
    user.commit();
}

void save(String name, Integer age) {
        SharedPreferences preferences = context.getSharedPreferences("test", Context.MODE_PRIVATE);
        Editor editor = preferences.edit();
        editor.putString("name", name);
        editor.putInt("age", age);
        editor.commit();
    }

数据读取与写入的方法都非常简单,只是在写入的时候有些区别:先调用edit()使其处于编辑状态,然后才能修改数据,最后使用commit()提交修改的数据。实际上SharedPreferences是采用了XML格式将数据存储到设备中,在DDMS中的File Explorer中的/data/data//shares_prefs下。以上面的数据存储结果为例,打开后可以看到一个user_info.xml的文件,打开后可以看到:

<?xml version=”1.0″ encoding=”UTF-8″?>
<map>
    <string name=”NAME”>moandroid</string>
    <string name=” PASSWORD”>SharedPreferences</string>
</map>

使用SharedPreferences是有些限制的:只能在同一个包内使用,不能在不同的包之间使用。

2.文件存储数据
文件存储方式是一种较常用的方法,在Android中读取/写入文件的方法,与Java中实现I/O的程序是完全一样的,提供了openFileInput()和openFileOutput()方法来读取设备上的文件。FilterInputStream, FilterOutputStream等可以到Java io package说明中去详细学习,不再此详细说明,具体实例如下:

    String fn = “moandroid.log”;
    FileInputStream fis = openFileInput(fn);
    FileOutputStream fos = openFileOutput(fn,Context.MODE_PRIVATE);

文件操作模式:

文件(四种操作模式)
FileOutputStream fos =
Context.openFileOutput(filename,Context.MODE_PRIVATE; Context.MODE_APPEND;
Context.MODE_WORLD_READABLE+Context.MODE_WORLD_WRITEABLE )

除此之外,Android还提供了其他函数来操作文件,详细说明请阅读Android SDK。

3.网络存储数据
网络存储方式

利用HttpURLConnection对象,我们可以从网络中获取网页数据.

URL url = new URL("http://www.sohu.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5* 1000);//设置连接超时
conn.setRequestMethod(“GET”);//以get方式发起请求
if (conn.getResponseCode() != 200) throw new RuntimeException("请求url失败");
InputStream is = conn.getInputStream();//得到网络返回的输入流
String result = readData(is, "GBK");
conn.disconnect();
//第一个参数为输入流,第二个参数为字符集编码
public static String readData(InputStream inSream, String charsetName) throws Exception{
    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len = -1;
    while( (len = inSream.read(buffer)) != -1 ){
        outStream.write(buffer, 0, len);
    }
    byte[] data = outStream.toByteArray();
    outStream.close();
    inSream.close();
    return new String(data, charsetName);
}

4.ContentProvider

1. ContentProvider简介

当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。

2. Uri类简介

Uri代表了要操作的数据,Uri主要包含了两部分信息:1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。
2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
要操作contact表中id为10的记录,可以构建这样的路径:/contact/10
要操作contact表中id为10的记录的name字段, contact/10/name
要操作contact表中的所有记录,可以构建这样的路径:/contact
要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下:
要操作xml文件中contact节点下的name节点,可以构建这样的路径:/contact/name
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")

3. UriMatcher、ContentUrist和ContentResolver简介

因为Uri代表了要操作的数据,所以我们很经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。
UriMatcher:用于匹配Uri,它的用法如下:
1.首先把你需要匹配Uri路径全部给注册上,如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回匹配码为1
uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact”, 1);//添加需要匹配uri,如果匹配就会返回匹配码
//如果match()方法匹配 content://com.changcheng.sqlite.provider.contactprovider/contact/230路径,返回匹配码为2
uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact/#”, 2);//#号为通配符

2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回的匹配码为1。

ContentUris:用于获取Uri路径后面的ID部分,它有两个比较实用的方法:
withAppendedId(uri, id)用于为路径加上ID部分
parseId(uri)方法用于从路径中获取ID部分
ContentResolver:当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。

总结说明

以上5中存储方式,在以后的开发过程中,根据设计目标、性能需求、空间需求等找到合适的数据存储方式。Android 中的数据存储都是私有的,其他应用程序都是无法访问的,除非通过ContentResolver获取其他程序共享的数据。采用文件方式对外共享数据,需要进行文件操作读写数据;
采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。