출처
: http://oic.tstore.co.kr/front/community/mentoring/viewMentoring.action?seq=180
: http://regularmotion.kr/android-sqlite-content-providers-thread-safety/
: http://arabiannight.tistory.com/entry/180
[요약]
Contentprovider 는 uri 값을 주어서 데이터를 가져 오는 방식입니다.
상대 앱의 authorities가 포함된 uri을 호출해도 처음 몇번은 불러오더라도 제대로 불려오지 못하는 경우가 있습니다.
그 이유는 provider에 exported가 true로 설정되어 있지 않아서 발생하는 이유입니다.
<provider android:name="MyProvider" android:exported="true" android:authorities="com.tacademy.myprovider"> </provider> |
출처: http://regularmotion.kr/android-sqlite-content-providers-thread-safety/
[Android] SQLite, Content Providers, & Thread Safety.
이 글은 http://www.androiddesignpatterns.com에 소개된 글을 저자의 동의를 얻고 번역한 것임을 알려드립니다.
원문 : http://www.androiddesignpatterns.com/2012/10/sqlite-contentprovider-thread-safety.html
ContentProvider를 사용할 때 가장 햇갈리는 부분은 thread-safety다.
작업이 오래 걸릴 수 있는 query에 대해 asynchronous(background)하게 처리해서 UI thread를 방해(block)하지 말아야 된다는
사실은 알고 있다. 그렇다면 multiple thread에서 ContentProvider에 접근해서 사용해도 괜찮은 건가?
Threads and Content Providers
ContentProvider에 대한 문서를 참고하면 method가 multiple thread에서 호출 될 수 있기 때문에,
반드시 thread-safe하게 처리되야 한다고 주의하고 있다.
Data access methods (such as insert(Uri, ContentValues) and update(Uri, ContentValues, String, String[])) may be called from many threads at once, and must be thread-safe.
다른 말로, “Android는 ContentProvider로의 접근을 동기화 해주지 않는다”는 말이고,
만약 서로 다른 thread에서 동일한 method를 동시에 요청하면 둘 중 하나는 반드시 대기해야 한다.
ContentProvider를 사용하려면 Framework 개발자의 입장에서 이러한 동시성(Concurrency) 문제를 직접 처리해줘야 한다.
Ensuring Thread Safety
이제 ContentProvider가 thread safe하지 않다는 사실을 알았다.
그럼 잠재적으로 발생할 수 있는 race condition을 어떻게 제거할 수 있나?
그냥 모든 method에 synchronized 접근자를 추가하면 되나?
ContentProvider가 데이터를 저장하기 위해 SQLiteDatabase를 사용한다는 사실을 감안하면 그럴 필요까지는 없을 것 같다.
SQLiteDatabase 문서를 참고하면, SQLiteDatabase는 기본적으로 동기화됐기 때문에 2개의 thread가 동시에 접근할 수 없다.
따라서 각각의 ContentProvider method 사용에 synchronize 접근자를 사용하여 동기화하는 것은 불필요하다.
Conclusion
ContentProvider는 thread-safe를 보장하지 않지만, race condition을 보장하기 위해 추가 구현은 불필요하다는 사실을
종종 볼 수 있다.
ContentProvider가 SQLiteDatabase를 기반으로 하고 있다는 사실을 감안하면, 2개의 thread가 동시에 data를 추가(write)하려고
시도하면 SQLiteDatabase는 먼저 들어온 하나의 thread가 작업을 완전히 종료할때까지 다른 하나의 thread를 대기 시킨다.
따라서 각각의 thread는 상호 배제적(mutually exclusive)으로 데이터에 접근할 수 있고, thread safe하다는 사실을 보장 받는다.
즉, ContentProvider는 thread safe를 보장하지 않지만, ContentProvider가 데이터를 저장하기 위해 사용하는
SQLiteDatabase는 thread safe를 보장하기 때문에 ContentProvider의 각각의 method에 synchronized 접근자를
추가하는 것은 불필요 할 때가 많다.
안드로이드/Android 컨텐트 프로바이더(ContentProvider) 사용법 1 - data -
< 개발 환경 > < 프로젝트 적용 > |
우선 저번 시간에 보았던 ContentProvider의 기본적인 동작 원리 입니다.
1. ContentProvider 를 구현한 어플리케이션 |
Auth_key를 만들고 ContentProvider를 통해 공유하는 방법으로 만들어 졌습니다.
실제 개발에서는 Auth_key 를 관리하면서 진행해 주시면 됩니다.
AndroidManifest.xml 파일 입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!--?xml version= "1.0" encoding= "utf-8" ?--> <manifest xmlns:android= "http://schemas.android.com/apk/res/android" package = "arabiannight.tistory.com.contentproviderdataa" android:versioncode= "1" android:versionname= "1.0" > <uses-sdk android:minsdkversion= "8" android:targetsdkversion= "8" > <application android:allowbackup= "true" android:icon= "@drawable/ic_launcher" android:label= "@string/app_name" android:theme= "@style/AppTheme" > <activity android:name= "arabiannight.tistory.com.contentproviderdataa.activity.MainActivity" android:label= "@string/app_name" > <intent-filter> <action android:name= "android.intent.action.MAIN" > <category android:name= "android.intent.category.LAUNCHER" > </category></action></intent-filter> </activity> <!-- Provider 등록 authorities로 Content Provider 구분 --> <provider android:name= ".provider.DataProvider" android:authorities= "arabiannight.tistory.com.contentproviderdataa" > </provider></application> </uses-sdk></manifest> |
DataProvider.java 파일 입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | package arabiannight.tistory.com.contentproviderdataa.provider; import java.util.List; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.util.Log; /** * ContentProvider Class */ public class DataProvider extends ContentProvider{ public static final String AUTHORITY = "arabiannight.tistory.com.contentproviderdataa" ; public static final Uri CONTENT_URI = private String Auth_key = "K333KAAARABIANUUUU" ; public String getAuthkey() { return Auth_key; } public void setAuthkey(String authkey) { Auth_key = authkey; } @Override public String getType(Uri uri) { return null ; } /** * ContentProvider 객체가 생성 되면 호출 */ @Override public boolean onCreate() { return false ; } @Override public Uri insert(Uri uri, ContentValues values) { Log.d( "PROVIDERT" , "A_insert()" ); List<string> reqValue = uri.getPathSegments(); if (reqValue.size() > 0 ) { String serviceType = reqValue.get( 0 ); Log.d( "PROVIDERT" , "A_serviceType = " + serviceType); if (serviceType.equals( "AUTH_GET" )){ return Uri.parse(CONTENT_URI + "/" + serviceType + "/" + getAuthkey()); } } return uri; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null ; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.d( "PROVIDERT" , "A_update()" ); List<string> reqValue = uri.getPathSegments(); if (reqValue.size() > 0 ) { String serviceType = reqValue.get( 0 ); Log.d( "PROVIDERT" , "A_serviceType = " + serviceType); if (serviceType.equals( "AUTH_UPDATE" )){ String new_authkey = values.getAsString( "new_authkey" ); Log.i( "PROVIDERT" , "update() new_authkey = " + new_authkey); setAuthkey(new_authkey); } } return 0 ; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0 ; } } //</string></string> |
2. ContentProvider를 Call(요청) 하는 어플리케이션 |
ContentProvider에 접근하기 위해서는 "content://AUTHORITY" 를 사용해서 접근 하시면 됩니다.
"content://" 는 웹페이지 접속 할때 사용하는 "http://address.com" 의 "http://" 형식 처럼 안드로이드에서 ContentProvider 에
접근할 때 사용하는 규약 이라고 생각 하시면 됩니다.
AUTHORITY = ContentProvider를 구현한 어플리케이션 AndroidManifest.xml 파일에 선언한 authorities 와 동일하게 작성해주면 됩니다.
MainActivity.java 파일 입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | package arabiannight.tistory.com.contentproviderdatab; import java.util.List; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView; /** * ContentProvider Call Activity */ public class MainActivity extends Activity { public static final String AUTHORITY = "arabiannight.tistory.com.contentproviderdataa" ; /** ContentProvider 제공 클래스에서 받을 uri.getPathSegments()를 등록해 준다 * << content://" + AUTHORITY + PATH_GET>> 다음부터 getPathSegments[0] = PATH_GET, * [1], [2], [3]... 순으로 나간다. */ public static final String PATH_GET = "/AUTH_GET" ; public static final String PATH_UPDATE = "/AUTH_UPDATE" ; /** CotentProvider 접근을 위한 ContentResolver 객체를 생성할 때 넣어 주는 매개변수에 * URI를 사용 한다. */ public static final Uri CONTENT_URI = public static final Uri CONTENT_URI2 = @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); setLayout(); } public void onClick(View v) { switch (v.getId()) { case R.id.bt_get: Log.i( "PROVIDERT" , "B Click Auth get Button!" ); // ContentResolver 객체 얻어 오기 ContentResolver cr = getContentResolver(); // ContentProviderDataA 어플리케이션 insert() 메서드에 접근 Uri uri = cr.insert(CONTENT_URI, new ContentValues()); // ContentProviderDataA 어플리케이션 에서 리턴받은 Data값 셋팅 하기 List<string> authValues = uri.getPathSegments(); String serviceType = authValues.get( 0 ); String authkey = authValues.get( 1 ); Log.i( "PROVIDERT" , "B_Return_serviceType = " + serviceType); Log.i( "PROVIDERT" , "B_Return_authkey = " + authkey); tv_Content.setText( "A 어플리케이션에서 받아온 인증키 : " + authkey); break ; case R.id.bt_update: // ContentResolver 객체 얻어 오기 ContentResolver cr2 = getContentResolver(); // ContentValuse를 사용한 Data 전달하기 ContentValues cv = new ContentValues(); cv.put( "new_authkey" , "SECONDeww33000aaacccxx" ); // ContentProviderDataA 어플리케이션 update() 메서드에 접근 cr2.update(CONTENT_URI2, cv, null , null ); break ; default : break ; } } /** * Layout */ private TextView tv_Content; private void setLayout() { tv_Content = (TextView) findViewById(R.id.tv_content); } } //</string> |
3. A 와 B 어플리케이션 사이에서의 Data 공유를 도와주는 ContentResolver |
기본적으로 Data공유를 위해서는 insert() 메서드만 가지고 공유작업을 진행할 수 있습니다. 위의 예제에서 update() 메서드에 구현된 작업을 insert() 메서드에 대신 해줘도 상관 없습니다. 자 그럼 이렇게 해서 기본적인 Data 공유에 대해 알아 봤습니다. 다음 포스팅에서는 DB를 활용한 ContentProvider 구현에 대해 알아 보도록 하겠습니다.
파일첨부 :
스크린샷 :
감사합니다.
'IT_Programming > Android_Java' 카테고리의 다른 글
[펌] 잠자는 앱을 깨우자. (FLAG_INCLUDE_STOPPED_PACKAGES) (0) | 2012.10.05 |
---|---|
[펌] ViewPager에 적용되는 Fragment View 재사용하기 (ViewHolder 적용) (0) | 2012.10.05 |
[펌] SparseArray 기능 소개 (HashMap 대용) (0) | 2012.10.05 |
[펌] PreferenceActivity 디자인 하기 (0) | 2012.10.05 |
[펌] Bitmap 생성 없이도 Bitmap 크기 정보를 구하는 방법 (0) | 2012.10.05 |