ContentResolver.requestSync কেন একটি সিঙ্ককে ট্রিগার করে না?


112

আমি গুগল আইও - স্লাইড 26 তে আলোচনা করা হিসাবে বিষয়বস্তু সরবরাহকারী-সিঙ্ক অ্যাডাপ্টার প্যাটার্নটি বাস্তবায়নের চেষ্টা করছি My আমার কন্টেন্ট সরবরাহকারী কাজ করছে, এবং যখন আমি কন্টেন্টআরসিভারকে কল করি তখন ডেভ টুলস সিঙ্ক টেস্টার অ্যাপ্লিকেশন থেকে এটি ট্রিগার করার সময় আমার সিঙ্কটি কাজ করে। আমার কন্টেন্টপ্রাইডার থেকে অনুরোধসিন্যাস (অ্যাকাউন্ট, কর্তৃপক্ষ, বান্ডিল), আমার সিঙ্কটি কখনই ট্রিগার হয় না।

ContentResolver.requestSync(
        account, 
        AUTHORITY, 
        new Bundle());

সম্পাদনা করুন - ম্যানিফেস্টের স্নিপেট যুক্ত হয়েছে আমার ম্যানিফেস্টে এক্সএমএল রয়েছে:

<service
    android:name=".sync.SyncService"
    android:exported="true">
    <intent-filter>
        <action
            android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data android:name="android.content.SyncAdapter"
    android:resource="@xml/syncadapter" />
</service>

--Edit

আমার সিঙ্কডাপ্টার.এক্সএমএলটি আমার সিঙ্ক পরিষেবার সাথে যুক্ত রয়েছে:

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"  
    android:contentAuthority="AUTHORITY"
    android:accountType="myaccounttype"
    android:supportsUploading="true"
/>

অন্যান্য কোডটি কী কার্যকর হবে তা নিশ্চিত নয়। অনুরোধে পাস করা অ্যাকাউন্টটি সিঙ্কটি "মাইকাউন্টাউন্টটাইপ" এর এবং এথোर्थিটি কলটিতে পাস হয়ে আমার সিসি অ্যাডাপ্টারের এক্সএমএল এর সাথে মেলে।

ContentResolver.requestSync একটি সিঙ্কের অনুরোধ করার সঠিক উপায়? দেখে মনে হচ্ছে সিঙ্ক পরীক্ষক সরঞ্জামটি সরাসরি পরিষেবার সাথে আবদ্ধ থাকে এবং কলগুলি সিঙ্ক সিঙ্ক শুরু করে, তবে মনে হয় এটি সিঙ্ক আর্কিটেকচারের সাথে সংহত করার উদ্দেশ্যকে পরাস্ত করে।

যদি এটি কোনও সিঙ্কের অনুরোধ করার সঠিক উপায় হয় তবে কেন সিঙ্ক টেস্টার কাজ করবে, তবে আমার কন্টেন্টআরসলভার.রেকেষ্টসাইনে নয়? বান্ডেলে আমার পাস করার মতো কিছু আছে কি?

আমি এমুলেটরটিতে 2.1 এবং 2.2 চলমান ডিভাইসগুলিতে পরীক্ষা করছি।


3
আমার সমস্যাটি হ'ল সিঙ্ক অ্যাডাপ্টারে আমার ব্রেক পয়েন্টটি পৌঁছাচ্ছে না ... তখন আমি বুঝতে পেরেছিলাম যে আমি একটি পরিষেবা ডিবাগ করার চেষ্টা করছি ... আশা করি এটি আমার মতো অন্যদেরও সহায়তা করবে।
ডাঙ্গালগ

2
সিঙ্ক অ্যাডাপ্টার পরিষেবাতে ব্রেকপয়েন্টগুলি ট্রিগার করতে ব্যর্থ হবে। কারণ সিঙ্ক অ্যাডাপ্টার পরিষেবাটি পৃথক প্রক্রিয়াতে চলে। @ ডাংল্যাং ইঙ্গিত দিচ্ছিল এটিই। এই প্রশ্নটিও দেখুন: স্ট্যাকওভারফ্লো.com
কনস্ট্যান্টিন

1
আমার ক্ষেত্রে android:process=":sync"সিঙ্ক পরিষেবা থেকে অপসারণ করাতে ডিবাগারটি চঞ্চির পয়েন্টগুলিতে আঘাত করতে দেয়। সিঙ্ক সার্ভিস নিজেই এর আগে কাজ করছিল, কারণ আমি onPerformSyncঅন্য প্রক্রিয়ার পক্ষে পদ্ধতি থেকে লগ বার্তা দেখতে পেতাম ।
সের্গেই

অন্য কারণ হ'ল ওয়াইফাই বন্ধ, সুতরাং আপনি যদি উপহাসিত ডেটার সাথে সিঙ্ক করার চেষ্টা করছেন তবে আপনার সংযোগটি পরীক্ষা করুন।
অ্যালান ভেলসো

উত্তর:


280

কলিং requestSync()কেবলমাত্র এমন কোনও {অ্যাকাউন্ট, কন্টেন্টঅর্ডারিটি} জুটিতে কাজ করবে যা সিস্টেমে পরিচিত। আপনার অ্যাপটিকে অ্যান্ড্রয়েডকে বলতে নির্দিষ্ট ধরণের পদক্ষেপের মধ্য দিয়ে যেতে হবে যে আপনি নির্দিষ্ট ধরণের অ্যাকাউন্ট ব্যবহার করে নির্দিষ্ট ধরণের সামগ্রী সিঙ্ক্রোনাইজ করতে সক্ষম। এটি অ্যান্ড্রয়েড ম্যানিফেস্টে এটি করে।

1. আপনার অ্যাপ্লিকেশন প্যাকেজ সিঙ্ক করে দেয় তা অ্যান্ড্রয়েডকে জানান

প্রথমে, অ্যান্ড্রয়েড ম্যানিফেস্ট.এক্সএমএলে আপনাকে ঘোষণা করতে হবে যে আপনার একটি সিঙ্ক পরিষেবা রয়েছে:

<service android:name=".sync.mySyncService" android:exported="true">
   <intent-filter>
      <action android:name="android.content.SyncAdapter" /> 
    </intent-filter>
    <meta-data 
        android:name="android.content.SyncAdapter" 
        android:resource="@xml/sync_myapp" /> 
</service>

<service>ট্যাগটির নামের বৈশিষ্ট্যটি হ'ল সিঙ্ক আপ সংযুক্ত করার জন্য আপনার শ্রেণির নাম ... আমি এটি এক সেকেন্ডের মধ্যে কথা বলব।

সত্য রফতানি করা সেটটি এটি অন্যান্য উপাদানগুলির কাছে দৃশ্যমান করে তোলে ( ContentResolverএটি যাতে কল করতে পারে তাই প্রয়োজন )।

অভিযুক্ত ফিল্টার এটিকে একটি উদ্দেশ্য অনুরোধকারী সিঙ্কটি ধরতে দেয়। ( আপনি যখন কল করবেন বা সম্পর্কিত সময়সূচী সম্পর্কিত পদ্ধতিগুলি Intentআসে ContentResolverতখনই এটি আসে ContentResolver.requestSync()))

<meta-data>ট্যাগ নিচে আলোচনা করা হবে।

২. অ্যান্ড্রয়েডকে আপনার সিঙ্কএডাপ্টার সন্ধান করতে ব্যবহৃত একটি পরিষেবা সরবরাহ করুন

ক্লাস নিজেই ... এখানে একটি উদাহরণ:

public class mySyncService extends Service {

    private static mySyncAdapter mSyncAdapter = null;

    public SyncService() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (mSyncAdapter == null) {
            mSyncAdapter = new mySyncAdapter(getApplicationContext(), true);
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
}

আপনার শ্রেণি অবশ্যই প্রসারিত Serviceকরতে হবে বা এর একটি সাবক্লাস অবশ্যই প্রয়োগ public IBinder onBind(Intent)করতে হবে এবং অবশ্যই SyncAdapterBinderযখন তাকে ডাকা হবে অবশ্যই ফিরে আসতে হবে ... আপনার টাইপের একটি পরিবর্তনশীল দরকার AbstractThreadedSyncAdapter। সুতরাং আপনি দেখতে পারেন যে, যে ক্লাসে বেশ কিছু সবকিছু। এটি কেবলমাত্র কোনও পরিষেবা সরবরাহ করার কারণ, যা আপনার SyncAdapterনিজের শ্রেণি সম্পর্কে নিজের ক্লাসটি অনুসন্ধান করার জন্য অ্যান্ড্রয়েডের জন্য একটি স্ট্যান্ডার্ড ইন্টারফেস সরবরাহ করে।

৩. class SyncAdapterআসলে সিঙ্কটি সম্পাদন করতে একটি সরবরাহ করুন।

mySyncAdapter হ'ল আসল সিঙ্ক যুক্তি নিজেই সংরক্ষণ করা হয়। onPerformSync()সিঙ্ক হওয়ার সময় হলে এর পদ্ধতিটি কল হয়ে যায়। আমি অনুভব করেছি যে আপনি ইতিমধ্যে এটি জায়গায় আছে।

৪) অ্যাকাউন্ট-টাইপ এবং একটি সামগ্রী কর্তৃপক্ষের মধ্যে একটি বাধ্যবাধকতা স্থাপন করুন

অ্যান্ড্রয়েড ম্যানিফেস্টে আবার ফিরে তাকাতে, <meta-data>আমাদের সেবারে সেই অদ্ভুত ট্যাগটি মূল বিষয়বস্তু যা একটি ContentAuthority এবং অ্যাকাউন্টের মধ্যে আবশ্যককে প্রতিষ্ঠিত করে। এটি বাহ্যিকভাবে অন্য একটি এক্সএমএল ফাইলের উল্লেখ করে (আপনার অ্যাপ্লিকেশনটির সাথে প্রাসঙ্গিক কিছু বলে এটি কল করুন) আসুন sync_myapp.xML দেখুন:

<?xml version="1.0" encoding="utf-8" ?> 
<sync-adapter 
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:contentAuthority="com.android.contacts"
    android:accountType="com.google" 
    android:userVisible="true" /> 

ঠিক আছে, তাহলে এটি কি করে? এটি অ্যান্ড্রয়েডকে বলে যে আমরা যে সিঙ্ক অ্যাডাপ্টারটি সংজ্ঞায়িত করেছি (সেই শ্রেণীর যে <service>ট্যাগটির নাম উপাদানটিতে ডাকা হত যেটি <meta-data>এই ফাইলটিকে রেফারেন্স করে তা অন্তর্ভুক্ত ...) একটি পরিচিতি সমন্বিত করবে একটি Gmail এ স্টাইল অ্যাকাউন্ট ব্যবহার করে।

আপনার সমস্ত সামগ্রীর অনুমোদনের স্ট্রিংগুলির সাথে সমস্ত মিল রয়েছে এবং আপনি যা সিঙ্ক করছেন তার সাথে মিল রয়েছে - এটি আপনি নির্ধারিত একটি স্ট্রিং হওয়া উচিত, যদি আপনি নিজের ডেটাবেস তৈরি করেন, বা যদি আপনি পরিচিত সিঙ্ক করছেন তবে কিছু ডিভাইস স্ট্রিং ব্যবহার করা উচিত উপাত্তের ধরণগুলি (যেমন পরিচিতি বা ক্যালেন্ডার ইভেন্টগুলি বা আপনার কাছে কী আছে)) উপরের ("com.android.contacts") পরিচিতি টাইপের ডেটার (আশ্চর্য, অবাক করা বিষয়) সামগ্রী বিষয়বস্তু স্ট্রিং হিসাবে ঘটে happens

অ্যাকাউন্টটাইপটি ইতিমধ্যে প্রবেশ করা সেই পরিচিত অ্যাকাউন্ট ধরণেরগুলির সাথে একটির সাথেও মিলতে হবে বা এটি আপনি তৈরি করছেন এমন একটিটির সাথে মিলতে হবে (এতে আপনার সার্ভারে প্রমাণ পেতে অ্যাকাউন্টআউটিক্যান্টেসরের একটি সাবক্লাস তৈরি করা আছে ... নিজেই নিবন্ধের মূল্য রয়েছে।) আবার, "কম গুগল" হ'ল সংজ্ঞায়িত স্ট্রিং ... google.com স্টাইল অ্যাকাউন্ট শংসাপত্রগুলি (আবার, এটি আশ্চর্য হওয়া উচিত নয়))

5. একটি প্রদত্ত অ্যাকাউন্ট / সামগ্রীর অনুমোদনের জুটিতে সিঙ্ক সক্ষম করুন

শেষ অবধি, সিঙ্ক সক্ষম করতে হবে। আপনি আপনার অ্যাপ্লিকেশনটিতে গিয়ে এবং ম্যাচের অ্যাকাউন্টের মধ্যে আপনার অ্যাপ্লিকেশনের পাশে চেকবক্সটি সেট করে নিয়ন্ত্রণ প্যানেলে অ্যাকাউন্টস এবং সিঙ্ক পৃষ্ঠায় এটি করতে পারেন। পর্যায়ক্রমে, আপনি এটি আপনার অ্যাপ্লিকেশনের কিছু সেটআপ কোডে করতে পারেন:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

সিঙ্কটি হওয়ার জন্য, আপনার অ্যাকাউন্ট / কর্তৃত্বের জুটি সিঙ্ক করতে সক্ষম হতে হবে (উপরের মতো) এবং সিস্টেমে সামগ্রিক বিশ্বব্যাপী সিঙ্ক পতাকা সেট করতে হবে এবং ডিভাইসটিতে অবশ্যই নেটওয়ার্ক সংযোগ থাকতে হবে।

যদি আপনার অ্যাকাউন্ট / কর্তৃপক্ষের সিঙ্ক বা গ্লোবাল সিঙ্কটি অক্ষম থাকে তবে রিকোয়েস্টসিঙ্ক () কে কল করার কোনও প্রভাব পড়ে - এটি একটি পতাকা সেট করে যা সিঙ্কের জন্য অনুরোধ করা হয়েছে, এবং সিঙ্ক সক্ষম হওয়ার সাথে সাথেই সম্পাদন করা হবে।

এছাড়াও, প্রতি মিলিভিভিতে , আপনার অনুরোধেরContentResolver.SYNC_EXTRAS_MANUAL অতিরিক্ত বান্ডিলটিতে সত্য হিসাবে সেট করে সিঙ্ক সিঙ্গেল অ্যান্ড্রয়েডকে বৈশ্বিক সিঙ্ক বন্ধ থাকলেও একটি সিঙ্ক জোর করতে বলবে (এখানে আপনার ব্যবহারকারীর প্রতি শ্রদ্ধাশীল হোন!)

অবশেষে, আপনি আবার ContentResolver ফাংশন সহ পর্যায়ক্রমিক নির্ধারিত সিঙ্ক সেটআপ করতে পারেন।

Multiple. একাধিক অ্যাকাউন্টের প্রভাবগুলি বিবেচনা করুন

একই ধরণের একাধিক অ্যাকাউন্ট থাকা সম্ভব (একটি ডিভাইসে দুটি @ gmail.com অ্যাকাউন্ট সেটআপ করা বা দুটি ফেসবুক অ্যাকাউন্ট, বা দুটি টুইটার অ্যাকাউন্ট, ইত্যাদি ...) আপনার এটি করার প্রয়োগের বিষয়টি বিবেচনা করা উচিত। .. আপনার যদি দুটি অ্যাকাউন্ট থাকে তবে আপনি সম্ভবত উভয়টিকে একই ডাটাবেস সারণিতে সিঙ্ক করার চেষ্টা করতে চান না। হতে পারে আপনাকে নির্দিষ্ট করতে হবে যে একবারে কেবল একজনই সক্রিয় হতে পারে এবং আপনি অ্যাকাউন্টগুলি স্যুইচ করলে টেবিলগুলি ফ্লাশ করে আবার সংযোগ স্থাপন করতে পারেন। (কোনও সম্পত্তি পৃষ্ঠার মাধ্যমে যা অ্যাকাউন্টগুলি উপস্থিত রয়েছে তা অনুসন্ধান করে)। হতে পারে আপনি প্রতিটি অ্যাকাউন্টের জন্য আলাদা আলাদা ডাটাবেস তৈরি করতে পারেন, বিভিন্ন টেবিল, প্রতিটি টেবিলে একটি কী কলাম হতে পারে। সমস্ত অ্যাপ্লিকেশন নির্দিষ্ট এবং কিছু চিন্তা যোগ্য। ContentResolver.setIsSyncable(Account account, String authority, int syncable)এখানে আগ্রহী হতে পারে। setSyncAutomatically()নিয়ন্ত্রণগুলি একটি অ্যাকাউন্ট / কর্তৃপক্ষ যুগল কিনা চেক করা বাচেক করা নেই , অন্যদিকে setIsSyncable()লাইনটি আনচেক এবং ধূসর করার একটি উপায় সরবরাহ করে যাতে ব্যবহারকারী এটি চালু না করতে পারে। আপনি একটি অ্যাকাউন্ট সিঙ্কেবল সেট করতে পারেন এবং অন্যটি সিঙ্কেবল নয় (ডিএসএবল)।

7. কন্টেন্টআরসলভার সম্পর্কে সচেতন হন not

একটি কৃপণ জিনিস। ContentResolver.notifyChange()এটি দ্বারা ব্যবহৃত একটি ফাংশনContentProviders এর অ্যান্ড্রয়েডকে অবহিত করতে যা স্থানীয় ডাটাবেস পরিবর্তিত হয়েছে। এটি দুটি ফাংশন পরিবেশন করে, প্রথমত, এটি সেই সামগ্রীটি ইউরি আপডেট করার পরে কার্সার সৃষ্টি করে এবং ফলস্বরূপ প্রয়োজনীয়তা অকার্যকর করে এবং একটি ListViewইত্যাদি পুনরায় আঁকায় ... এটি খুব যাদু, ডেটাবেস পরিবর্তিত হয় এবং ListViewস্বয়ংক্রিয়ভাবে আপনার ঠিক আপডেট হয়। অসাধারণ. এছাড়াও, যখন ডাটাবেস পরিবর্তন হয়, অ্যান্ড্রয়েড আপনার জন্য সাধারণ সিডিয়ালের বাইরে এমনকি সিঙ্কের জন্য অনুরোধ করবে, যাতে এই পরিবর্তনগুলি ডিভাইসটি সরিয়ে নিয়ে যায় এবং যত তাড়াতাড়ি সম্ভব সার্ভারে সিঙ্ক হয়ে যায়। দুর্দান্তও।

যদিও এর একটি কিনারা রয়েছে। যদি আপনি সার্ভারটি থেকে ContentProviderটানেন এবং কোনও আপডেটে ধাক্কা দেন তবে এটি কর্তব্য সহ কল notifyChange()করবে এবং অ্যান্ড্রয়েড চলে যাবে, "ওহ, ডাটাবেস পরিবর্তন হয়, সেগুলি সার্ভারে আরও ভাল রাখুন!" (দোহ!) ভাল-লিখিত ContentProvidersকিছু পরিবর্তন রয়েছে যা নেটওয়ার্ক থেকে বা ব্যবহারকারীর কাছ থেকে এসেছে কিনা তা পরীক্ষা করতে হবে এবং বুলিয়ান সেট করবেsyncToNetwork এই অপব্যয়কারী ডাবল-সিঙ্কটিকে রোধ করতে পতাকাটি মিথ্যা । যদি আপনি একটিতে ডেটা খাওয়ান থাকেন ContentProviderতবে কীভাবে এটি কাজ করা যায় তা নির্ধারণের পক্ষে এটি আপনাকে বোঝায় - অন্যথায় যখন কেবলমাত্র একটির প্রয়োজন হয় তখন আপনি সর্বদা দুটি সিঙ্ক সম্পাদন করবেন।

8. খুশি মনে হচ্ছে!

আপনি একবারে এই সমস্ত এক্সএমএল মেটাডেটা স্থানে রাখলে এবং সিঙ্ক সক্ষম হয়ে গেলে, অ্যান্ড্রয়েড কীভাবে আপনার জন্য সমস্ত কিছু সংযুক্ত করতে হয় তা জানবে এবং সিঙ্কের কাজ শুরু করা উচিত। এই মুহুর্তে, বেশ ভাল জিনিসগুলি কেবল জায়গায় ক্লিক করবে এবং এটি অনেকটা যাদুর মতো অনুভব করবে। উপভোগ করুন!


11
ContentResolver.setSync স্বয়ংক্রিয়ভাবে (অ্যাকাউন্ট, প্রমাণ, সত্য);
jcwenger

1
কোনও সমস্যা নেই, খুশি আমি সাহায্য করতে পারলাম। আবার শৈলীর দৃষ্টিকোণ থেকে, যদি "প্রমাণীকরণ" এবং "মায়াকাউন্টটিপ" হ'ল আপনি যে প্রকৃত স্ট্রিং ব্যবহার করছেন (এবং সাইটে কেবল অনুলিপি দেওয়ার জন্য নমুনা নয়), আপনার অবশ্যই নামকরণের সম্মেলনগুলি পরিষ্কার করতে হবে। এই স্ট্রিংগুলি সমস্ত ডিভাইস জুড়ে অনন্য হতে হবে এবং যদি অন্য কোনও প্রোগ্রামার অলসভাবে কর্তৃপক্ষের জন্য একটি ম্যাচিং স্ট্রিংয়ের সাথে একটি প্যাকেজ তৈরি করে এবং আপনি কোনও দ্বন্দ্ব পান তবে আপনি আসল সমস্যায় পড়বেন। চিয়ার্স!
jcwenger

22
গ্লোবাল সিঙ্ক সেটিংস বন্ধ থাকলেও আপনি একটি সিঙ্কের জন্য অনুরোধ করতে পারেন। কেবল ContentResolver.SYNC_EXTRAS_MANUALঅতিরিক্ত অতিরিক্ত
বান্ডিলের

2
@ ক্যাকিউলা: আমি কারও সম্পর্কে জানি না, তবে ডিভাইসটি মনে রাখবে যে এটি সিঙ্ক করা দরকার, এবং এটি বৈশ্বিক সিঙ্ক চালু হওয়ার সাথে সাথেই এটি বন্ধ হয়ে যাবে। আপনার সত্যিকার অর্থে এটিকে ব্যবহারকারীকে ট্রাম্প করার চেষ্টা করা উচিত নয় - বিশেষত ক্রোটিকাল পরিস্থিতিতে ব্যাটারি সংরক্ষণের অন্যতম প্রধান উপায় "গ্লোবাল সিঙ্ক অফ" as আপনি যদি ডেটা সিঙ্ক না হওয়ার বিষয়ে সত্যিই চিন্তিত হন তবে এমন একটি পপআপ বিবেচনা করুন যা ব্যবহারকারীকে ডেটাটি নাড়ানোর কারণ বলে, যদি এটি কিছুক্ষণ বসে থাকে। এইভাবে আপনি এমন ব্যবহারকারীদের শিক্ষিত করতে পারেন যা দুর্ঘটনাক্রমে তাদের ডিভাইসগুলির ভুল কনফিগার করেছে এবং বিদ্যুৎ-ব্যবহারকারীদের যদি তারা ভুলে যায় তবে মনে করিয়ে দিতে পারে।
jcwenger

2
এটি যোগ করার উপযুক্ত হতে পারে যে আপনি যদি অ্যাডপরিওডিকসাইক () ব্যবহার করতে চান তবে এটি কেবলমাত্র কাজ করে মনে হয় আপনি যদি সেট সিংকআউটমেটিক () সেট করে থাকেন - আমি হতাশার বাইরে যোগ করেছি, কিছু কাজ করার চেষ্টা করছি। আমি জানি এটি মূল প্রশ্নের অংশ ছিল না, তবে এটি এমন একটি সম্পূর্ণ উত্তর!
android.weasel

0

আমি অ্যাকাউন্টম্যানেজার পদ্ধতির setIsSyncableপরে কল করছিলাম setAuthToken। তবে setAuthTokenফাংশন ফিরে আসার আগেই setIsSyncable। অর্ডার পরিবর্তনের পরে সবকিছু ঠিকঠাক কাজ করেছে!

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.