ContentProvider 기능이 필요해서 가져다 사용 하려는데,
현재 검색되는 코드들은 대부분 되는 것들이 없어서 (코드 자체로는 전혀 문제가 없었지만 추가사항이 있어서..) 코드 공유 합니다.
기본적인 설명으로는 여기를
https://developer.android.com/guide/topics/providers/content-provider-creating.html?hl=ko
이외 코드 관련 부분은 아래에서 참고하였습니다.
참고URL : https://www.tutorialspoint.com/android/android_content_providers.htm
주의사항!
studio가 불안정한건지, 아님 저의 툴 설정이 문제인건지, 여러번 빌드시 이전 provider 설정들이 남아 있는건지
정상 동작하지 않던 문제가 있었습니다.
저같은 경우는 항상 A,B 앱을 uninstall 후에 빌드 시 제대로된 결과을 얻을 수 있었습니다.
(Permission Denial: opening provider com.app.first.myContentProvider from ..... 등등이 나온다면...)
1. A앱 작업 Mainfests (퍼미션을 추가합니다. 프로바이더 내에서도 readPermission과 writePermission , exprted를 명시합니다.)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.app.first">
<!-- 퍼미션 추가 -->
<permission android:name="com.app.first.READ_DATABASE" android:protectionLevel="normal" />
<permission android:name="com.app.first.WRITE_DATABASE" android:protectionLevel="normal" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 프로바이더 등록 -->
<provider android:name=".myContentProvider"
android:authorities="com.app.first.myContentProvider"
android:exported="true"
android:readPermission="com.app.first.READ_DATABASE"
android:writePermission="com.app.first.WRITE_DATABASE"/>
</application>
</manifest>
activity_main.xml (레이아웃 만들기)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context="com.app.first.MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="데이터 불러오기"
android:id="@+id/bt_renew"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_below="@id/bt_renew"
android:textSize="15sp"
android:text="Hello World!"
android:id="@+id/tv_text"/>
</RelativeLayout>
MainActivity.java (메인 화면 처리)
package com.app.first;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
DBHelper mDatabase;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDatabase = new DBHelper(getBaseContext());
// 데이터 입력
mDatabase.setDelete();
List<ItemRow> mList = new ArrayList<>();
mList.add(new ItemRow("식사","미역국",5000));
mList.add(new ItemRow("간식","우유",4000));
mList.add(new ItemRow("간식","바나나",3000));
mList.add(new ItemRow("식사","오이",2000));
mList.add(new ItemRow("식사","당근",1000));
for(ItemRow item : mList) {
mDatabase.setItem(item.contents, item.name, item.num);
}
Button bt_renew = (Button) findViewById(R.id.bt_renew);
bt_renew.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 데이터 불러오기
loadData();
}
});
}
private void loadData() {
StringBuilder sb = new StringBuilder();
List<ItemRow> row = mDatabase.getItem();
sb.append("Total count : "+row.size()+"\n\n");
for(ItemRow item : row) {
sb.append(item.contents+" , "+item.name+" , "+item.num+"\n");
}
TextView tv_text = (TextView) findViewById(R.id.tv_text);
tv_text.setText(sb.toString());
}
@Override
protected void onDestroy() {
super.onDestroy();
if(mDatabase != null)
mDatabase.close();
}
}
class ItemRow {
public String contents;
public String name;
public int num;
public ItemRow(String contents, String name, int num) {
this.contents = contents;
this.name = name;
this.num = num;
}
}
DBHelper.java (디비 로직 작업)
package com.app.first;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import java.util.ArrayList;
import java.util.List;
public class DBHelper extends SQLiteOpenHelper {
private static final int db_version = 1; // Database Version
private static final String DB_FILE_NAME = "UserData.db";
private static final String [] COLUMNS = {"contents TEXT", "name TEXT", "num INTEGER"};
public static String TABLE_NAME = "my_table";
public DBHelper(Context context) {
// TODO http://stackoverflow.com/questions/4547461/closing-the-database-in-a-contentprovider
super(context, DB_FILE_NAME, null, db_version);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table " + TABLE_NAME +
" (" + BaseColumns._ID + " integer primary key autoincrement ";
for (int i = 0; i < COLUMNS.length; i++) {
sql += ", " + COLUMNS[i];
}
sql += " ) ";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public List<ItemRow> getItem() {
List<ItemRow> list = new ArrayList<>();
try {
beginTransaction();
Cursor c = getAll(TABLE_NAME);
if (c != null) {
int total = c.getCount();
if (total > 0) {
c.moveToFirst();
while (!c.isAfterLast()) {
String contents = c.getString(1);
String name = c.getString(2);
int num = c.getInt(3);
list.add(new ItemRow(contents, name, num));
c.moveToNext();
}
}
c.close();
}
} catch (SQLiteException e) {
e.printStackTrace();
} finally {
endTransaction();
}
return list;
}
public void setItem(String contents, String name, int num) throws SQLiteException{
ContentValues values = new ContentValues();
values.put("contents", contents);
values.put("name", name);
values.put("num", num);
insert(TABLE_NAME, values);
}
public void setDelete() {
AllDelete(TABLE_NAME);
}
protected Cursor getAll(String tableName) throws SQLiteException {
return getReadableDatabase().query(tableName, null, null, null, null, null, /*"date desc"*/null);
}
protected void beginTransaction() {
getWritableDatabase().beginTransaction();
}
protected void endTransaction() {
getWritableDatabase().setTransactionSuccessful(); // db 속도 향상
getWritableDatabase().endTransaction();
}
protected void insert(String tableName, ContentValues values) throws SQLiteException {
getWritableDatabase().insert(tableName, null, values);
}
protected void AllDelete(String tableName) throws SQLiteException{
getWritableDatabase().delete(tableName, null, null);
}
}
myContentProvider.java (컨텐트프로바이더 내의 쿼리 처리)
package com.app.first;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.HashMap;
public class myContentProvider extends ContentProvider {
private SQLiteDatabase mDatabase;
static final String PROVIDER_NAME = "com.app.first.myContentProvider";
private static HashMap<String, String> STUDENTS_PROJECTION_MAP;
static final int GET_ALL = 1;
static final int INSERT = 2;
static final int UPDATE = 3;
static final int DELETE = 4;
static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "getAll", GET_ALL);
uriMatcher.addURI(PROVIDER_NAME, "insert", INSERT);
}
@Override
public boolean onCreate() {
DBHelper dbHelper = new DBHelper(getContext());
mDatabase = dbHelper.getWritableDatabase();
return (mDatabase == null)? false:true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.d("test","uri : "+uri);
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("my_table");
//switch (uriMatcher.match(uri)) {
// case STUDENTS:
// qb.setProjectionMap(STUDENTS_PROJECTION_MAP);
// break;
// case STUDENT_ID:
// qb.appendWhere( _ID + "=" + uri.getPathSegments().get(1));
// break;
// default:
// }
qb.setProjectionMap(STUDENTS_PROJECTION_MAP);
if (sortOrder == null || sortOrder == ""){
sortOrder = /*NAME*/"contents";
}
Cursor c = qb.query(mDatabase, projection, selection, selectionArgs,null, null, sortOrder);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
2. B앱 Mainfests (퍼미션 설정을 추가합니다.)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.app.second">
<!-- 퍼미션 설정 -->
<uses-permission android:name="com.app.first.READ_DATABASE" />
<uses-permission android:name="com.app.first.WRITE_DATABASE" />
<!-- 퍼미션 설정 -->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.app.second.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
MainActivity.java (A앱의 myContentProvider에 쿼리를 날려 데이터를 가져옵니다.)
package com.app.second;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
String authority = "com.app.first.myContentProvider";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Cursor c = getContentResolver().query(Uri.parse("content://"+authority+"/getdataall"), null, null, null, null);
if(c == null)
return;
Log.e("test","aaabbb "+c.getCount());
StringBuilder aa = new StringBuilder();
while(c.moveToNext()) {
String str = c.getLong(0) + " , " + c.getString(1)+" , "+c.getString(2)+" , "+c.getInt(3);
System.out.println(str);
aa.append(str+"\n");
}
c.close();
TextView text = (TextView) findViewById(R.id.text);
text.setText(aa);
}
}
3. 결과 화면
A앱 B앱