一、Android数据存储,参考。
①最熟悉的是文件存储的方式,分两种:
1、保存在手机的文件目录中(也可以是缓存目录中,使用了Context对象),
2、保存在sd卡上(使用了Environment对象获取SD卡信息,参考:)。
保存文件时的选择:不太重要的可以存在缓存目录中(可以直接被删除);较重要的存在一般目录中;最重要的可以存在SD卡上。
②使用SharedPreferences保存数据同样是保存文件形式,但是是xml格式的文件。
Android解析xml文件的方式:Android提供了多种解析器,但是推荐使用pull解析器。参考:.
③使用SQlite数据库存储数据,和mysql等相比的特点是:在客户端,不用安装服务器。Android中通过两个对象来进行操作:SQLiteOpenHelper,SQLiteDatabase。
参考:。
④使用内容提供者:这是应用之间传递数据的方式,一个应用作为提供者,另一个进行查询(这也是使用SQLite数据库,但是是对另一个用的数据库进行CURD操作)。
1、提供者将数据操作的类(dao类)继承ContentProviders即可将其暴露,并通过UriMatcher对象设置访问路径且只有uri匹配时才进行相应操作。
注意:此时的dao类需要实现ContentProviders的抽象方法来进行CURD操作;内容提供者可以设置权限进行控制访问。
2、在配置文件中进行配置(包含其本身的声明和权限的配置)
1 import android.content.ContentProvider; 2 import android.content.ContentUris; 3 import android.content.ContentValues; 4 import android.content.UriMatcher; 5 import android.database.Cursor; 6 import android.database.sqlite.SQLiteDatabase; 7 import android.net.Uri; 8 9 public class PersonContentProvider extends ContentProvider { 10 11 private static final String AUTHORITY = "com.itheima28.sqlitedemo.providers.PersonContentProvider"; 12 private static final int PRESON_INSERT_CODE = 0; // 操作person表添加的操作的uri匹配码 13 private static final int PERSON_DELETE_CODE = 1; 14 private static final int PERSON_UPDATE_CODE = 2; 15 private static final int PERSON_QUERY_ALL_CODE = 3; 16 private static final int PERSON_QUERY_ITEM_CODE = 4; 17 18 private static UriMatcher uriMatcher; 19 private PersonSQLiteOpenHelper mOpenHelper; // person表的数据库帮助对象 20 21 static { 22 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 23 // 添加一些uri(类似于分机号) 24 25 // content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/insert 26 uriMatcher.addURI(AUTHORITY, "person/insert", PRESON_INSERT_CODE); 27 28 // content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/delete 29 uriMatcher.addURI(AUTHORITY, "person/delete", PERSON_DELETE_CODE); 30 31 // content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/update 32 uriMatcher.addURI(AUTHORITY, "person/update", PERSON_UPDATE_CODE); 33 34 // content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/queryAll 35 uriMatcher.addURI(AUTHORITY, "person/queryAll", PERSON_QUERY_ALL_CODE); 36 37 // content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/query/# 38 uriMatcher.addURI(AUTHORITY, "person/query/#", PERSON_QUERY_ITEM_CODE); 39 } 40 41 //这里可以通过该方法进行初始化,而不用通过构造函数 42 @Override 43 public boolean onCreate() { 44 mOpenHelper = new PersonSQLiteOpenHelper(getContext()); 45 return true; 46 } 47 48 //这个方法是由系统调用的,用于说明返回值的MIME类型 49 @Override 50 public String getType(Uri uri) { 51 switch (uriMatcher.match(uri)) { 52 case PERSON_QUERY_ALL_CODE: // 返回多条的MIME-type 53 return "vnd.android.cursor.dir/person"; 54 case PERSON_QUERY_ITEM_CODE: // 返回单条的MIME-TYPE 55 return "vnd.android.cursor.item/person"; 56 default: 57 break; 58 } 59 return null; 60 } 61 62 @Override 63 public Cursor query(Uri uri, String[] projection, String selection, 64 String[] selectionArgs, String sortOrder) { 65 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 66 switch (uriMatcher.match(uri)) { 67 case PERSON_QUERY_ALL_CODE: // 查询所有人的uri 68 if(db.isOpen()) { 69 Cursor cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder); 70 return cursor; 71 // db.close(); 返回cursor结果集时, 不可以关闭数据库 72 } 73 break; 74 case PERSON_QUERY_ITEM_CODE: // 查询的是单条数据, uri末尾出有一个id 75 if(db.isOpen()) { 76 77 long id = ContentUris.parseId(uri); 78 79 Cursor cursor = db.query("person", projection, "_id = ?", new String[]{id + ""}, null, null, sortOrder); 80 81 return cursor; 82 } 83 break; 84 default: 85 throw new IllegalArgumentException("uri不匹配: " + uri); 86 } 87 return null; 88 } 89 90 @Override 91 public Uri insert(Uri uri, ContentValues values) { 92 93 switch (uriMatcher.match(uri)) { 94 case PRESON_INSERT_CODE: // 添加人到person表中 95 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 96 97 if(db.isOpen()) { 98 99 long id = db.insert("person", null, values);100 101 db.close();102 103 return ContentUris.withAppendedId(uri, id);104 }105 break;106 default:107 throw new IllegalArgumentException("uri不匹配: " + uri);108 }109 return null;110 }111 112 @Override113 public int delete(Uri uri, String selection, String[] selectionArgs) {114 switch (uriMatcher.match(uri)) {115 case PERSON_DELETE_CODE: // 在person表中删除数据的操作116 SQLiteDatabase db = mOpenHelper.getWritableDatabase();117 if(db.isOpen()) {118 int count = db.delete("person", selection, selectionArgs);119 db.close();120 return count;121 }122 break;123 default:124 throw new IllegalArgumentException("uri不匹配: " + uri);125 }126 return 0;127 }128 129 @Override130 public int update(Uri uri, ContentValues values, String selection,131 String[] selectionArgs) {132 switch (uriMatcher.match(uri)) {133 case PERSON_UPDATE_CODE: // 更新person表的操作134 SQLiteDatabase db = mOpenHelper.getWritableDatabase();135 if(db.isOpen()) {136 int count = db.update("person", values, selection, selectionArgs);137 db.close();138 return count;139 }140 break;141 default:142 throw new IllegalArgumentException("uri不匹配: " + uri);143 }144 return 0;145 }146 147 }
1 import android.content.ContentResolver; 2 import android.content.ContentUris; 3 import android.content.ContentValues; 4 import android.database.Cursor; 5 import android.net.Uri; 6 import android.test.AndroidTestCase; 7 import android.util.Log; 8 9 public class TextCase extends AndroidTestCase {10 11 private static final String TAG = "TextCase";12 13 public void testInsert() {14 Uri uri = Uri.parse("content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/insert");15 16 // 内容提供者访问对象17 ContentResolver resolver = getContext().getContentResolver();18 19 ContentValues values = new ContentValues();20 values.put("name", "fengjie");21 values.put("age", 90);22 23 uri = resolver.insert(uri, values);24 Log.i(TAG, "uri: " + uri);25 long id = ContentUris.parseId(uri);26 Log.i(TAG, "添加到: " + id);27 }28 29 public void testDelete() {30 Uri uri = Uri.parse("content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/delete");31 32 // 内容提供者访问对象33 ContentResolver resolver = getContext().getContentResolver();34 35 String where = "_id = ?";36 String[] selectionArgs = {"21"};37 int count = resolver.delete(uri, where, selectionArgs);38 Log.i(TAG, "删除行: " + count);39 }40 41 public void testUpdate() {42 Uri uri = Uri.parse("content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/update");43 44 // 内容提供者访问对象45 ContentResolver resolver = getContext().getContentResolver();46 47 ContentValues values = new ContentValues();48 values.put("name", "lisi");49 50 int count = resolver.update(uri, values, "_id = ?", new String[]{"20"});51 Log.i(TAG, "更新行: " + count);52 }53 54 public void testQueryAll() {55 Uri uri = Uri.parse("content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/queryAll");56 57 // 内容提供者访问对象58 ContentResolver resolver = getContext().getContentResolver();59 60 Cursor cursor = resolver.query(uri, new String[]{"_id", "name", "age"}, null, null, "_id desc");61 62 if(cursor != null && cursor.getCount() > 0) {63 64 int id;65 String name;66 int age;67 while(cursor.moveToNext()) {68 id = cursor.getInt(0);69 name = cursor.getString(1);70 age = cursor.getInt(2);71 Log.i(TAG, "id: " + id + ", name: " + name + ", age: " + age);72 }73 cursor.close();74 }75 }76 77 public void testQuerySingleItem() {78 Uri uri = Uri.parse("content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/query/#");79 80 // 在uri的末尾添加一个id content://com.itheima28.sqlitedemo.providers.PersonContentProvider/person/query/2081 uri = ContentUris.withAppendedId(uri, 20);82 83 // 内容提供者访问对象84 ContentResolver resolver = getContext().getContentResolver();85 86 Cursor cursor = resolver.query(uri, new String[]{"_id", "name", "age"}, null, null, null);87 88 if(cursor != null && cursor.moveToFirst()) {89 int id = cursor.getInt(0);90 String name = cursor.getString(1);91 int age = cursor.getInt(2);92 cursor.close();93 Log.i(TAG, "id: " + id + ", name: " + name + ", age: " + age);94 }95 }96 }
内容提供者是Android四大组件之一,是系统数据向外提供的方式,例如短信,联系人信息等,参考:。
和内容提供者相关的是内容观察者,指的是监听一个uri路径上的数据库变化并作出相应操作的类,参考:,注意这篇文章中的其他链接也有些不错的好文。
参考:.
⑤网络数据:Android是一个操作系统,里面各种应用进行网络操作,所以需要多种网络访问方式,而不像web中只有http即可,这就需要所谓的网络编程。参考:。
但是移动端网络性能对用户体验影响大,所以通常需要多线程处理网络访问或者其他比较复杂的问题,这就涉及到多线程编程,而在多个线程之间进行消息的传递也是一个必须处理的问题,参考:。
1 import java.io.InputStream; 2 import java.net.HttpURLConnection; 3 import java.net.MalformedURLException; 4 import java.net.URL; 5 6 import javax.net.ssl.HttpsURLConnection; 7 8 import android.os.Bundle; 9 import android.os.Handler; 10 import android.os.Message; 11 import android.app.Activity; 12 import android.graphics.Bitmap; 13 import android.graphics.BitmapFactory; 14 import android.util.Log; 15 import android.view.Menu; 16 import android.view.View; 17 import android.view.View.OnClickListener; 18 import android.widget.EditText; 19 import android.widget.ImageView; 20 import android.widget.Toast; 21 22 public class MainActivity extends Activity implements OnClickListener { 23 24 private static final String TAG = "MainActivity"; 25 protected static final int ERROR = 1; 26 private EditText etUrl; 27 private ImageView ivIcon; 28 private final int SUCCESS = 0; 29 30 private Handler handler = new Handler() { 31 32 /** 33 * 接收消息 34 */ 35 @Override 36 public void handleMessage(Message msg) { 37 super.handleMessage(msg); 38 39 Log.i(TAG, "what = " + msg.what); 40 if(msg.what == SUCCESS) { // 当前是访问网络, 去显示图片 41 ivIcon.setImageBitmap((Bitmap) msg.obj); // 设置imageView显示的图片 42 } else if(msg.what == ERROR) { 43 Toast.makeText(MainActivity.this, "抓去失败", 0).show(); 44 } 45 } 46 }; 47 48 @Override 49 protected void onCreate(Bundle savedInstanceState) { 50 super.onCreate(savedInstanceState); 51 setContentView(R.layout.activity_main); 52 53 ivIcon = (ImageView) findViewById(R.id.iv_icon); 54 etUrl = (EditText) findViewById(R.id.et_url); 55 56 findViewById(R.id.btn_submit).setOnClickListener(this); 57 } 58 59 @Override 60 public void onClick(View v) { 61 final String url = etUrl.getText().toString(); 62 63 new Thread(new Runnable() { 64 65 @Override 66 public void run() { 67 Bitmap bitmap = getImageFromNet(url); 68 69 // ivIcon.setImageBitmap(bitmap); // 设置imageView显示的图片 70 if(bitmap != null) { 71 Message msg = new Message(); 72 msg.what = SUCCESS; 73 msg.obj = bitmap; 74 handler.sendMessage(msg); 75 } else { 76 Message msg = new Message(); 77 msg.what = ERROR; 78 handler.sendMessage(msg); 79 } 80 }}).start(); 81 82 } 83 84 /** 85 * 根据url连接取网络抓去图片返回 86 * @param url 87 * @return url对应的图片 88 */ 89 private Bitmap getImageFromNet(String url) { 90 HttpURLConnection conn = null; 91 try { 92 URL mURL = new URL(url); // 创建一个url对象 93 94 // 得到http的连接对象 95 conn = (HttpURLConnection) mURL.openConnection(); 96 97 conn.setRequestMethod("GET"); // 设置请求方法为Get 98 conn.setConnectTimeout(10000); // 设置连接服务器的超时时间, 如果超过10秒钟, 没有连接成功, 会抛异常 99 conn.setReadTimeout(5000); // 设置读取数据时超时时间, 如果超过5秒, 抛异常100 101 conn.connect(); // 开始链接102 103 int responseCode = conn.getResponseCode(); // 得到服务器的响应码104 if(responseCode == 200) {105 // 访问成功106 InputStream is = conn.getInputStream(); // 获得服务器返回的流数据107 108 Bitmap bitmap = BitmapFactory.decodeStream(is); // 根据 流数据 创建一个bitmap位图对象109 110 return bitmap;111 } else {112 Log.i(TAG, "访问失败: responseCode = " + responseCode);113 }114 } catch (Exception e) {115 e.printStackTrace();116 } finally {117 if(conn != null) {118 conn.disconnect(); // 断开连接119 }120 }121 return null;122 }123 }
Android下进行http连接可以使用HttpClient类,但是在Android 6.0(即sdk23)之后移除了相关包,所以不推荐使用,但是好像有的框架依然在使用,可以作为了解,对于Android下使用参考:关于其他的使用参考:。注意其中引用的文章列表。
!!!网络访问在模拟器下测试时,访问本机的路径不是127.0.0.1,而是10.0.2.2,或者是局域网中的路径。参考:.
二、android权限
①Android文件权限是linux风格的
②安卓的权限非常细,而且应用需要申请,而用户可以对其进行设定(在模拟器中直接赋予),
具体权限参考:。
三、多线程下载:
上图的要点是:
①RandomAccessFile类,这是一个非常适合用于多线程下载的类,因为它带有一个文件指针,可以方便的针对文件的指定位置进行操作。所以多个线程可以分别在不同的位置操作同一个文件而不出线程问题。参考:。注意:rwd是直接将读取的文件写入到硬盘上,而rw是先写到内存的缓冲区中,当达到一定量之后在写入硬盘。两者各有优点:一个防止特殊情况丢失下载数据(还没有保存就断网等情况),一个减少io操作。但是下载时使用rwd更好。
②上面提到了多线程经常应用在网络等比较耗时的方面,对于网络下载更是如此。但是在Android下的下载使用的依然是j2se中的API,也就是HttpURLConnection类中的方法。具体实现的原理就是HTTP协议中的HTTP头 Range和Content-Range字段。参考:。
所谓的端点下载就是在用一个临时文件同步存储下载数据,只有在下载完成时才删除,所以这个文件就用于判断是否下载完成和记录下载到了那里,便于之后继续下载。
1 import java.io.BufferedReader; 2 import java.io.File; 3 import java.io.FileInputStream; 4 import java.io.InputStream; 5 import java.io.InputStreamReader; 6 import java.io.RandomAccessFile; 7 import java.net.HttpURLConnection; 8 import java.net.URL; 9 10 public class MutileThreadDownload { 11 /** 12 * 线程的数量 13 */ 14 private static int threadCount = 3; 15 16 /** 17 * 每个下载区块的大小 18 */ 19 private static long blocksize; 20 21 /** 22 * 正在运行的线程的数量 23 */ 24 private static int runningThreadCount; 25 26 /** 27 * @param args 28 * @throws Exception 29 */ 30 public static void main(String[] args) throws Exception { 31 // 服务器文件的路径 32 String path = "http://192.168.1.100:8080/ff.exe"; 33 URL url = new URL(path); 34 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 35 conn.setRequestMethod("GET"); 36 conn.setConnectTimeout(5000); 37 int code = conn.getResponseCode(); 38 if (code == 200) { 39 long size = conn.getContentLength();// 得到服务端返回的文件的大小 40 System.out.println("服务器文件的大小:" + size); 41 blocksize = size / threadCount; 42 // 1.首先在本地创建一个大小跟服务器一模一样的空白文件。 43 File file = new File("temp.exe"); 44 RandomAccessFile raf = new RandomAccessFile(file, "rw"); 45 raf.setLength(size); 46 // 2.开启若干个子线程分别去下载对应的资源。 47 runningThreadCount = threadCount; 48 for (int i = 1; i <= threadCount; i++) { 49 long startIndex = (i - 1) * blocksize; 50 long endIndex = i * blocksize - 1; 51 if (i == threadCount) { 52 // 最后一个线程 53 endIndex = size - 1; 54 } 55 System.out.println("开启线程:" + i + "下载的位置:" + startIndex + "~" 56 + endIndex); 57 new DownloadThread(path, i, startIndex, endIndex).start(); 58 } 59 } 60 conn.disconnect(); 61 } 62 63 private static class DownloadThread extends Thread { 64 private int threadId; 65 private long startIndex; 66 private long endIndex; 67 private String path; 68 69 public DownloadThread(String path, int threadId, long startIndex, 70 long endIndex) { 71 this.path = path; 72 this.threadId = threadId; 73 this.startIndex = startIndex; 74 this.endIndex = endIndex; 75 } 76 77 @Override 78 public void run() { 79 try { 80 // 当前线程下载的总大小 81 int total = 0; 82 File positionFile = new File(threadId + ".txt"); 83 URL url = new URL(path); 84 HttpURLConnection conn = (HttpURLConnection) url 85 .openConnection(); 86 conn.setRequestMethod("GET"); 87 // 接着从上一次的位置继续下载数据 88 if (positionFile.exists() && positionFile.length() > 0) { // 判断是否有记录 89 FileInputStream fis = new FileInputStream(positionFile); 90 BufferedReader br = new BufferedReader( 91 new InputStreamReader(fis)); 92 // 获取当前线程上次下载的总大小是多少 93 String lasttotalstr = br.readLine(); 94 int lastTotal = Integer.valueOf(lasttotalstr); 95 System.out.println("上次线程" + threadId + "下载的总大小:" 96 + lastTotal); 97 startIndex += lastTotal; 98 total += lastTotal;// 加上上次下载的总大小。 99 fis.close();100 }101 102 conn.setRequestProperty("Range", "bytes=" + startIndex + "-"103 + endIndex);104 conn.setConnectTimeout(5000);105 int code = conn.getResponseCode();106 System.out.println("code=" + code);107 InputStream is = conn.getInputStream();108 File file = new File("temp.exe");109 RandomAccessFile raf = new RandomAccessFile(file, "rw");110 // 指定文件开始写的位置。111 raf.seek(startIndex);112 System.out.println("第" + threadId + "个线程:写文件的开始位置:"113 + String.valueOf(startIndex));114 int len = 0;115 byte[] buffer = new byte[512];116 while ((len = is.read(buffer)) != -1) {117 RandomAccessFile rf = new RandomAccessFile(positionFile,118 "rwd");119 raf.write(buffer, 0, len);120 total += len;121 rf.write(String.valueOf(total).getBytes());122 rf.close();123 }124 is.close();125 raf.close();126 127 } catch (Exception e) {128 e.printStackTrace();129 } finally {130 // 只有所有的线程都下载完毕后 才可以删除记录文件。131 synchronized (MutileThreadDownload.class) {132 System.out.println("线程" + threadId + "下载完毕了");133 runningThreadCount--;134 if (runningThreadCount < 1) {135 System.out.println("所有的线程都工作完毕了。删除临时记录的文件");136 for (int i = 1; i <= threadCount; i++) {137 File f = new File(i + ".txt");138 System.out.println(f.delete());139 }140 }141 }142 143 }144 }145 }146 }
在Android上实现多线程下载的基本原理一样,只是有一些问题需要注意,
①下载文件的保存位置,Android项目需要保存在指定的位置:sd卡/Android项目文件路径
②多线程之间的信息传递
③对用户的提示