先看效果图:
这就是一个可以联网的图库软件,下面我们来看看需求
业务需求
1.判断是否第一次运行,第一次运行,提示添加新条目
2.点击添加按钮,弹出对话框,输入图片网址和标题
3.下载图片保存到本地SD卡中
4.数据库中保存图片文件路径和图片标题和URL地址
5.listview中列出已保存的所有条目,添加条目后,同步展现到listview中
6.选中listview中一个条目,点击删除,删除存储的条目,同步展现到listview
7.长按listview的条目,弹出删除菜单项,点击菜单项也可以删除条目
8.提供contentprovider供其他软件访问数据库
问题分析
虽然要求看起来挺多但是可以大致分为3部分去实现
- 数据的下载保存,包括存储到本地sdcard 和数据库中
- 数据的删除
- 为其他软件提供数据库接口
主要代码实现
首先用户输入图片地址我们应该去下载图片并保存到本地,此时下载图片属于耗时且需要联网的操作所以不能在ui线程中实现,我们创建异步任务AsyncTask完成图片下载,并通知UI线程更新进度条界面。下载完成应该返回图片的保存地址准备将数据写入数据库中 由于要展示下载的进度我们利用接口回调比较好实现。下面是下载图片的异步任务代码:
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.os.AsyncTask;
import android.os.Environment;
public class MyTask extends AsyncTask<String, Integer, String> {
public interface CallBack {
public void start(); //主界面展示一个进度条
public void updataProgress(int progress); //更新进度条
public void finish(String imgPath); //下载完成返回文件保存到绝对路径
}
CallBack cb;
public MyTask(CallBack cb) {
super();
this.cb = cb;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (cb != null) {
cb.start(); //准备工作
}
}
@Override
protected String doInBackground(String... params) {
// 1.HttpURLConnection
HttpURLConnection conn = null;
String imgPath = null;
// 2.URL
try {
URL url = new URL(params[0]);
// 3.url.openConnection
conn = (HttpURLConnection) url.openConnection();
// 4.InputStream
InputStream in = conn.getInputStream();
// 获取该文件的总长度
int total = conn.getContentLength();
// 5.获取保存文件的路径及文件 名/sdcard/image
String path_sdcard = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/image";
File fileParent = new File(path_sdcard);
// 判断该目录是否存在,如果不存在,创建该目录
if (!fileParent.exists()) {
// 创建目录
fileParent.mkdirs();
}
String arr[] = params[0].split("/");
String filenameString = arr[arr.length - 1];
// 6.创建File对象,再拿到OutputStream
File file = new File(path_sdcard, filenameString);
if (file.exists()) {
return file.getAbsolutePath();
}
// 用来返回该img路径
imgPath = file.getAbsolutePath();
OutputStream out = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int sum = 0;
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
// 累加
sum = sum + len;
// 计算百分比
int per = (int) (sum * 100f / total);
// 发布进度值
publishProgress(per);
}
out.flush();
out.close();
in.close();
// 返回当前被保存的img的绝对路径
return imgPath;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
//下载异常返回NULL
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
// TODO Auto-generated method stub
super.onProgressUpdate(values);
if (cb != null) {
cb.updataProgress(values[0]); //更新进度条
}
}
/*
* result表示的是图片所在的的路径
*/
@Override
protected void onPostExecute(String result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
if (cb != null) {
cb.finish(result); //返回图片地址
}
}
}
图片下载完成并且保存到本地了 此时我们应该将图片名称 URL 绝对路径 写入数据库。 那么此时我们应该开始创建数据库了,自定义MySqliteHelper 继承SQLiteOpenHelper 就好了,代码如下
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class MySqliteHelper extends SQLiteOpenHelper {
public MySqliteHelper(Context context) {
super(context, "picture.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 创表
String sql = "create table img(_id integer primary key autoincrement,name text ,url text,path text)";
db.execSQL(sql);
Log.d("onCreateDataBase", "helper onCreate create table img");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
}
数据库有着落了,那么我们应该想着如何将数据展示到listview中,这里ListView中的每个item包含了一张图片一个文本 。 这里就用就灵活的BaseAdapter完成,我们自定义一个MyAdapter继承自BaseAdapter。
import java.io.File;
import java.util.ArrayList;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
public class MyAdapter extends BaseAdapter {
ArrayList<Picture> data;
Context context;
LayoutInflater inflater;
int progress;
public MyAdapter(ArrayList<Picture> data, Context context) {
super();
this.data = data;
this.context = context;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.list_item, null);
holder.tv_1 = (TextView) convertView.findViewById(R.id.tv);
holder.iv = (ImageView) convertView.findViewById(R.id.img);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Picture picture = data.get(position);
File file = new File(picture.path);
if (file.exists()) { // 如果sd卡的图片存在去设置图片
holder.tv_1.setText(picture.name);
Bitmap bitmap = BitmapFactory.decodeFile(picture.path);
holder.iv.setImageBitmap(bitmap);
} else { // 图片不存在提示用户
Toast.makeText(context, picture.name + "好像出了点问题,图片是否被你删除?",
Toast.LENGTH_SHORT).show();
// 本地图片被删除 删除数据库中的数据
MySqliteHelper helper = new MySqliteHelper(context);
SQLiteDatabase db = helper.getWritableDatabase();
String sql = "DELETE FROM img WHERE path = '" + picture.path + "'";
db.execSQL(sql);
}
return convertView;
}
class ViewHolder {
TextView tv_1;
ImageView iv;
}
}
数据展示问题也解决了,那么 接下来就是删除事件,点击listview 或者长按listview中的item都有对应的监听事件分别是setOnItemClickListener、 setOnItemLongClickListener
删除事件的实现:
lv.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
final int position, long id) {
AlertDialog.Builder builder = new AlertDialog.Builder(
MainActivity.this);
builder.setTitle("刪除" + data.get(position).name).setMessage(
"此操作不可逆,是否继续?");
// 相当于确定
builder.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
/*
* 注释部分为普通sql查询语句 String sql =
* "delete from img where url='" +
* data.get(position).url + "'";
* db.execSQL(sql);
*/
// 1.获取连接服务地址
Uri uri = Uri
.parse("content://com.picture.provider");
// 2.获取ContentResolver
ContentResolver cr = getContentResolver();
// 3.根据位置获取到数据库中相应的url 作为条件删除相应的数据
cr.delete(uri, "url='" + data.get(position).url
+ "'", null);
File file = new File(data.get(position).path);
if (file.exists()) { // 如果存在那么删除本地文件
file.delete();
}
data = readDataBase(); // 读取数据库中的内容
MainActivity.this.position = -1;// 每次删除完毕设置当前位置为-1 为下一次点击做准备
adapter.notifyDataSetChanged();// 提示更新界面
}
});
// 相当于取消 这里啥都不干
builder.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
}
});
builder.show();
return false;
}
});
public void del(View view) {// 删除图片
if (position != -1) {
AlertDialog.Builder builder = new AlertDialog.Builder(
MainActivity.this);
builder.setTitle("刪除" + data.get(position).name).setMessage(
"此操作不可逆,是否继续?");
// 相当于确定
builder.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
/*
* String sql = "delete from img where url='" +
* data.get(position).url + "'"; db.execSQL(sql);
*/
Uri uri = Uri
.parse("content://com.picture.provider");
// 获取ContentResolver
ContentResolver cr = getContentResolver();
// 准备数据
cr.delete(uri, "url='" + data.get(position).url
+ "'", null);
File file = new File(data.get(position).path);
if (file.exists()) { // 如果存在那么删除本地文件
file.delete();
}
data = readDataBase(); // 读取数据库中的内容
if (data.size() == 0) { // 没有东西那么接下的链接可以直接下载
flag = true;
MainActivity.this.position = -1;
}
adapter.notifyDataSetChanged();// 提示更新界面
}
});
// 相当于取消
builder.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
} else {
Toast.makeText(this, "当前没有选中任何图片!", Toast.LENGTH_LONG).show();
}
}
删除功能也实现了,接下来我们实现ContentProvider功能其实也简单,写一个类继承自ContentProvider
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
public class MyContentProvider extends ContentProvider {
MySqliteHelper helper;
@Override
public boolean onCreate() {
helper = new MySqliteHelper(getContext());
if (helper != null) {
return true;
}
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query("img", projection, selection, selectionArgs,
null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri); // 通知界面更新
return cursor;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.e("insert", " " + uri.getAuthority());
SQLiteDatabase db = helper.getWritableDatabase();
long id = -1;
id = db.insert("img", null, values);
db.close();
getContext().getContentResolver().notifyChange(uri, null);
return ContentUris.withAppendedId(uri, id);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.e("delete", "delete");
SQLiteDatabase db = helper.getWritableDatabase();
int count = db.delete("img", selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return count; //返回删除的条数
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
Log.e("update", "update"+selection);
SQLiteDatabase db = helper.getWritableDatabase();
int count = db.update("img", values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}
在xml中注册provider提供给其他程序
<provider
android:name="com.wenxiangli.MyContentProvider"
android:authorities="com.picture.provider"
android:exported="true" >
</provider>
至此我们需要的功能都实现了。接下来说几个遇到的问题注意点:
1.权限一定不要忘记了
<!-- 联网权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- SDcard的读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 读取Sdcard状态权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
2.为了完成第二次启动加载本地图片,我们应该先从数据库读出数据然后绑定到集合中去通知adapter更新
3.删除特别注意越界问题,这里我是通过每次删除结束设置positon为-1,当点击事件产生将改变position的值去判断是否删除。
4.保证图库中的数据唯一,所以每次下载前判断图片是否存在,存在就不去下载。直接提示是否更改文件的名字
下面附上完整源码地址需要的可以在这下载
链接:http://pan.baidu.com/s/1dEW5JBr 密码:m3jb