आरामदायक एपीआई सेवा


226

मैं एक सेवा बनाने के लिए देख रहा हूँ जिसका उपयोग मैं एक वेब-आधारित REST API पर कॉल करने के लिए कर सकता हूँ।

मूल रूप से मैं ऐप इनिट पर एक सेवा शुरू करना चाहता हूं फिर मैं उस सेवा से एक url का अनुरोध करने और परिणाम वापस करने में सक्षम होना चाहता हूं। इस बीच मैं एक प्रगति विंडो या कुछ समान प्रदर्शित करने में सक्षम होना चाहता हूं।

मैंने वर्तमान में एक सेवा बनाई है जो IDL का उपयोग करती है, मैंने कहीं पढ़ा है कि आपको केवल क्रॉस ऐप संचार के लिए इसकी आवश्यकता है, इसलिए सोचें कि इन जरूरतों को अलग करना होगा लेकिन इसके बिना कॉलबैक करने के लिए अनिश्चित। जब मैं post(Config.getURL("login"), values)ऐप को हिट करता हूं तो थोड़ी देर के लिए रुकने लगता है (अजीब लगता है - एक सेवा के पीछे का विचार यह था कि यह एक अलग धागे पर चलता है!)

वर्तमान में मेरे पास पोस्ट के साथ एक सेवा है और http तरीकों के अंदर मिलता है, AIDL फ़ाइलों के एक जोड़े (दो तरह से संचार के लिए), एक ServiceManager जो सेवा को शुरू करने, रोकने, बांधने आदि से संबंधित है और मैं गतिशील रूप से विशिष्ट कोड के साथ एक हैंडलर बना रहा हूं आवश्यकतानुसार कॉलबैक के लिए।

मैं नहीं चाहता कि कोई मुझे काम करने के लिए एक पूर्ण कोड आधार दे, लेकिन कुछ संकेत बहुत सराहना करेंगे।

कोड (अधिकतर) पूर्ण:

public class RestfulAPIService extends Service  {

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
    return binder;
}
public void onCreate() {
    super.onCreate();
}
public void onDestroy() {
    super.onDestroy();
    mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
    public void doLogin(String username, String password) {

        Message msg = new Message();
        Bundle data = new Bundle();
        HashMap<String, String> values = new HashMap<String, String>();
        values.put("username", username);
        values.put("password", password);
        String result = post(Config.getURL("login"), values);
        data.putString("response", result);
        msg.setData(data);
        msg.what = Config.ACTION_LOGIN;
        mHandler.sendMessage(msg);
    }

    public void registerCallback(IRemoteServiceCallback cb) {
        if (cb != null)
            mCallbacks.register(cb);
    }
};

private final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        // Broadcast to all clients the new value.
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                switch (msg.what) {
                case Config.ACTION_LOGIN:
                    mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
                    break;
                default:
                    super.handleMessage(msg);
                    return;

                }
            } catch (RemoteException e) {
            }
        }
        mCallbacks.finishBroadcast();
    }
    public String post(String url, HashMap<String, String> namePairs) {...}
    public String get(String url) {...}
};

AIDL फ़ाइलों की एक जोड़ी:

package com.something.android

oneway interface IRemoteServiceCallback {
    void userLogIn(String result);
}

तथा

package com.something.android
import com.something.android.IRemoteServiceCallback;

interface IRestfulService {
    void doLogin(in String username, in String password);
    void registerCallback(IRemoteServiceCallback cb);
}

और सेवा प्रबंधक:

public class ServiceManager {

    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
    public IRestfulService restfulService;
    private RestfulServiceConnection conn;
    private boolean started = false;
    private Context context;

    public ServiceManager(Context context) {
        this.context = context;
    }

    public void startService() {
        if (started) {
            Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.startService(i);
            started = true;
        }
    }

    public void stopService() {
        if (!started) {
            Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.stopService(i);
            started = false;
        }
    }

    public void bindService() {
        if (conn == null) {
            conn = new RestfulServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.bindService(i, conn, Context.BIND_AUTO_CREATE);
        } else {
            Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

    protected void destroy() {
        releaseService();
    }

    private void releaseService() {
        if (conn != null) {
            context.unbindService(conn);
            conn = null;
            Log.d(LOG_TAG, "unbindService()");
        } else {
            Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
        }
    }

    class RestfulServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className, IBinder boundService) {
            restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
            try {
            restfulService.registerCallback(mCallback);
            } catch (RemoteException e) {}
        }

        public void onServiceDisconnected(ComponentName className) {
            restfulService = null;
        }
    };

    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        public void userLogIn(String result) throws RemoteException {
            mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));

        }
    };

    private Handler mHandler;

    public void setHandler(Handler handler) {
        mHandler = handler;
    }
}

सेवा init और बाइंड:

// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);

सेवा समारोह कॉल:

// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();

application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);

try {
    servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
    e.printStackTrace();
}

...later in the same file...

Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        switch (msg.what) {
        case Config.ACTION_LOGIN:

            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }

            try {
                ...process login results...
                }
            } catch (JSONException e) {
                Log.e("JSON", "There was an error parsing the JSON", e);
            }
            break;
        default:
            super.handleMessage(msg);
        }

    }

};

5
Android REST क्लाइंट कार्यान्वयन सीखने वाले लोगों के लिए यह बहुत उपयोगी हो सकता है। Dobjanschi की प्रस्तुति एक पीडीएफ में प्रसारित की गई: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…
Kay Zed

चूँकि बहुत से लोगों ने वीरगिल डोबज़न्स्की की प्रस्तुति की सिफारिश की और IO 2010 का लिंक अब टूट गया है, यहाँ YT वीडियो का सीधा लिंक है: youtube.com/watch?v=xHXn3Kg2IQE
जोस_गैस

जवाबों:


283

यदि आपकी सेवा आपके आवेदन का हिस्सा बनने जा रही है, तो आप इसे और अधिक जटिल बना सकते हैं, जितना कि इसे होना चाहिए। चूंकि आपके पास एक RESTful वेब सेवा से कुछ डेटा प्राप्त करने का एक सरल उपयोग मामला है, इसलिए आपको ResultReceiver और IntentService में देखना चाहिए ।

यह सेवा + ResultReceiver पैटर्न startService () के साथ सेवा को शुरू या बाध्य करके काम करता है जब आप कुछ कार्रवाई करना चाहते हैं। आप इंटेंस में एक्स्ट्रा के माध्यम से अपने ResultReceiver (गतिविधि) को करने और पास करने के लिए ऑपरेशन निर्दिष्ट कर सकते हैं।

जिस सेवा में आप ऑनहैंडलइंटेंट को लागू करते हैं, वह कार्य उस ऑपरेशन को करने के लिए होता है जो आशय में निर्दिष्ट होता है। जब ऑपरेशन पूरा हो जाता है, तो आप ResultReceiver में पारित का उपयोग करते हुए एक संदेश वापस गतिविधि पर भेजते हैं जिस बिंदु पर onReceiveResult कहा जाएगा।

उदाहरण के लिए, आप अपनी वेब सेवा से कुछ डेटा खींचना चाहते हैं।

  1. आप आशय बनाएँ और startService को कॉल करें।
  2. सेवा में संचालन शुरू होता है और यह गतिविधि को एक संदेश भेजता है जिसमें कहा गया है कि यह शुरू हो गया है
  3. गतिविधि संदेश को संसाधित करती है और प्रगति दिखाती है।
  4. सेवा ऑपरेशन को पूरा करती है और आपकी गतिविधि पर कुछ डेटा वापस भेजती है।
  5. आपकी गतिविधि डेटा को संसाधित करती है और सूची दृश्य में रखती है
  6. सेवा आपको यह कहते हुए एक संदेश भेजती है कि यह किया जाता है, और यह खुद को मारता है।
  7. गतिविधि को समाप्त संदेश मिलता है और प्रगति संवाद को छुपाता है।

मुझे पता है कि आपने उल्लेख किया था कि आपको एक कोड आधार नहीं चाहिए, लेकिन Google I / O 2010 ऐप का खुला स्रोत इस तरह से एक सेवा का उपयोग करता है जिसका मैं वर्णन कर रहा हूं।

नमूना कोड जोड़ने के लिए अपडेट किया गया:

वह काम।

public class HomeActivity extends Activity implements MyResultReceiver.Receiver {

    public MyResultReceiver mReceiver;

    public void onCreate(Bundle savedInstanceState) {
        mReceiver = new MyResultReceiver(new Handler());
        mReceiver.setReceiver(this);
        ...
        final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
        intent.putExtra("receiver", mReceiver);
        intent.putExtra("command", "query");
        startService(intent);
    }

    public void onPause() {
        mReceiver.setReceiver(null); // clear receiver so no leaks.
    }

    public void onReceiveResult(int resultCode, Bundle resultData) {
        switch (resultCode) {
        case RUNNING:
            //show progress
            break;
        case FINISHED:
            List results = resultData.getParcelableList("results");
            // do something interesting
            // hide progress
            break;
        case ERROR:
            // handle the error;
            break;
    }
}

सेवा:

public class QueryService extends IntentService {
    protected void onHandleIntent(Intent intent) {
        final ResultReceiver receiver = intent.getParcelableExtra("receiver");
        String command = intent.getStringExtra("command");
        Bundle b = new Bundle();
        if(command.equals("query") {
            receiver.send(STATUS_RUNNING, Bundle.EMPTY);
            try {
                // get some data or something           
                b.putParcelableArrayList("results", results);
                receiver.send(STATUS_FINISHED, b)
            } catch(Exception e) {
                b.putString(Intent.EXTRA_TEXT, e.toString());
                receiver.send(STATUS_ERROR, b);
            }    
        }
    }
}

ResultReceiver एक्सटेंशन - MyResultReceiver.Receiver को लागू करने के बारे में संपादित किया गया

public class MyResultReceiver implements ResultReceiver {
    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

    public interface Receiver {
        public void onReceiveResult(int resultCode, Bundle resultData);
    }

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }
}

1
बहुत बड़िया धन्यवाद! मैंने Google iosched एप्लिकेशन के माध्यम से जाना समाप्त कर दिया और वाह ... यह बहुत जटिल है, लेकिन मेरे पास कुछ है जो काम कर रहा है, अब मुझे बस काम करने की आवश्यकता है कि यह क्यों काम करता है! लेकिन हां, यह मूल पैटर्न है जो मैंने काम किया है। आपका बहुत बहुत धन्यवाद।
मार्टिन

29
उत्तर के लिए एक छोटा सा जोड़: जैसा कि आप mReceiver.setReceiver (नल) करते हैं; ऑनपॉज़ विधि में, आपको mReceiver.setReceiver (यह) करना चाहिए; onResume विधि में। यदि आपकी गतिविधि फिर से बनाए बिना फिर से शुरू की जाती है, तो आप घटनाओं को प्राप्त नहीं कर सकते हैं
विंसेंट मिमौन-प्रात

7
डॉक्स ने यह नहीं कहा कि आपको स्टॉप कॉल करने की आवश्यकता नहीं है, क्योंकि इंटेंटसेवा आपके लिए क्या करता है?
मिकेल ओहलसन

2
@MikaelOhlson सही, आपको कॉल नहीं करना चाहिए stopSelfयदि आप उपवर्ग करते हैं IntentServiceक्योंकि यदि आप करते हैं, तो आप किसी भी लंबित अनुरोध को उसी में खो देंगे IntentService
शांतिकाल

1
IntentServiceकार्य पूरा होने पर खुद को मार देगा, इसलिए this.stopSelf()अनावश्यक है।
एक

17

Android REST क्लाइंट एप्लिकेशन विकसित करना मेरे लिए एक बहुत बढ़िया संसाधन रहा है। स्पीकर किसी भी कोड को नहीं दिखाता है, वह बस एंड्रॉइड में एक रॉक सॉलिड रेस्ट एपी को एक साथ रखने के लिए डिजाइन विचार और तकनीकों पर जाता है। यदि आपका पॉडकास्ट थोड़े व्यक्ति है या नहीं, तो मैं इसे कम से कम एक सुनने की सलाह दूंगा, लेकिन व्यक्तिगत रूप से मैंने इसे 4 या पांच बार सुना है और अब तक मैं इसे फिर से सुनने जा रहा हूं।

एंड्रॉइड
रीस्ट क्लाइंट एप्लिकेशन विकसित करना लेखक: वर्जिल डॉबांसची
विवरण:

यह सत्र Android प्लेटफ़ॉर्म पर Restful अनुप्रयोगों को विकसित करने के लिए वास्तु विचार प्रस्तुत करेगा। यह एंड्रॉइड प्लेटफॉर्म के लिए विशिष्ट डिजाइन पैटर्न, प्लेटफॉर्म एकीकरण और प्रदर्शन के मुद्दों पर केंद्रित है।

और वहाँ बहुत सारे विचार हैं जो मैंने वास्तव में अपने एपीआई के पहले संस्करण में नहीं किए थे कि मुझे रिफ्लेक्टर करना पड़ा है


4
+1 इसमें उन प्रकार के विचार शामिल हैं जिनके बारे में आप कभी नहीं सोचते कि आप कब शुरू करेंगे।
थॉमस अहले

हाँ, रेस्ट क्लाइंट विकसित करने में मेरा पहला विचार लगभग उसका वर्णन था कि वास्तव में क्या नहीं करना चाहिए (शुक्र है कि मुझे यह देखने से पहले पता चला कि यह बहुत गलत था)। मैं उस पर थोड़ा सा बोला।
टेरेंस

मैंने इस वीडियो को एक से अधिक बार देखा है और मैं दूसरा पैटर्न लागू कर रहा हूं। मेरी समस्या यह है कि मुझे सर्वर से आने वाले ताज़ा डेटा से स्थानीय डेटा को अपडेट करने के लिए अपने जटिल डेटाबेस मॉडल में लेनदेन का उपयोग करने की आवश्यकता है, और ContentProvider इंटरफ़ेस मुझे इसे करने का एक तरीका प्रदान नहीं करता है। क्या आपके पास कोई सुझाव है, टेरेंस?
फ्लेवियो फारिया

2
नायब: HttpClient पर Dobjanschi द्वारा टिप्पणियाँ अब पकड़ नहीं है। देखें stackoverflow.com/a/15524143/939250
डोनल लाफर्टी

हाँ, अब HttpURLConnection पसंद किया गया है। इसके अलावा, एंड्रॉइड 6.0 की रिहाई के साथ, Apache HTTP क्लाइंट के लिए आधिकारिक तौर पर समर्थन हटा दिया गया है
रॉनआर

16

जब मैं पोस्ट को हिट करता हूं (Config.getURL ("लॉगिन"), मान) एप्लिकेशन को थोड़ी देर के लिए विराम लगता है (अजीब लगता है - एक सेवा के पीछे विचार यह था कि यह एक अलग धागे पर चलता है!)

नहीं, आपको स्वयं एक थ्रेड बनाना होगा , डिफ़ॉल्ट रूप से UI थ्रेड में एक स्थानीय सेवा चलती है।


11

मुझे पता है कि @Martyn को पूरा कोड नहीं चाहिए, लेकिन मुझे लगता है कि इस एनोटेशन से इस सवाल का अच्छा पता चलता है:

10 ओपन सोर्स एंड्रॉइड ऐप जो हर एंड्रॉइड डेवलपर को देखना चाहिए

एंड्रॉइड के लिए फोरस्क्वेयर ओपन-सोर्स है , और एक दिलचस्प कोड पैटर्न है जो फोरस्क्वेयर रेस्ट एपीआई के साथ इंटरैक्ट करता है।


6

मैं REST क्लाइंट रिट्रोफिट की अत्यधिक अनुशंसा करूंगा ।

मैंने इस अच्छी तरह से लिखे गए ब्लॉग पोस्ट को बेहद उपयोगी पाया है, इसमें सरल उदाहरण कोड भी है। लेखक डेटा कॉल पैटर्न को लागू करने के लिए नेटवर्क कॉल और ओटो बनाने के लिए रेट्रोफिट का उपयोग करता है :

http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html


5

बस मैं आप सभी को एक स्टैंडअलोन वर्ग की दिशा में इंगित करना चाहता था जिसे मैंने रोल किया था जिसमें सभी कार्यक्षमता शामिल है।

http://github.com/StlTenny/RestService

यह अनुरोध को गैर-अवरोधक के रूप में निष्पादित करता है, और हैंडलर को लागू करने के लिए एक आसान परिणाम देता है। यहां तक ​​कि एक उदाहरण कार्यान्वयन के साथ आता है।


4

आइए हम एक बटन पर एक घटना - onItemClicked () पर सेवा शुरू करना चाहते हैं। रिसीवर तंत्र उस स्थिति में काम नहीं करेगा क्योंकि: -
a) मैंने रिसीवर को सेवा में (इंटेंट एक्स्ट्रा में) onItemClicked ()
b) गतिविधि से पृष्ठभूमि में ले जाया । ऑनपॉज़ () में मैंने गतिविधि को लीक करने से बचने के लिए ResultReceiver के भीतर रिसीवर संदर्भ को शून्य करने के लिए सेट किया है।
c) गतिविधि नष्ट हो जाती है।
d) गतिविधि फिर से बन जाती है। हालाँकि इस बिंदु पर सेवा गतिविधि को कॉलबैक नहीं कर पाएगी क्योंकि रिसीवर संदर्भ खो जाता है।
एक सीमित प्रसारण या एक PendingIntent का तंत्र ऐसे परिदृश्यों में अधिक उपयोगी लगता है- सेवा से गतिविधि को सूचित करने का संदर्भ दें


1
आप जो कह रहे हैं उससे समस्या है। वह यह है कि जब गतिविधि पृष्ठभूमि पर जाती है तो वह नष्ट नहीं होती है ... इसलिए रिसीवर अभी भी मौजूद है और गतिविधि संदर्भ भी।
डीएआरकेओ

@DArkO जब किसी गतिविधि को रोक दिया जाता है या उसे कम स्मृति स्थितियों में Android सिस्टम द्वारा मारा जा सकता है। गतिविधि जीवनचक्र का संदर्भ लें ।
jk7

4

ध्यान दें कि रॉबी पॉन्ड के समाधान में किसी भी तरह की कमी है: इस तरह से आप केवल एक समय में एक एपि को कॉल करने की अनुमति देते हैं क्योंकि इंटेंटसेवा केवल एक समय में एक इरादे को संभालती है। अक्सर आप समानांतर एपी कॉल करना चाहते हैं। यदि आप इसे चाहते हैं, तो आपको IntentService के बजाय सेवा का विस्तार करना होगा और अपना स्वयं का थ्रेड बनाना होगा।


1
आप अभी भी IntentService पर कई कॉल कर सकते हैं, जिसमें वेब्स सर्विस एपीआई कॉल करके एक निष्पादक-थ्रेड-सर्विस को प्रस्तुत करते हैं, जो आपके इंटेंटसेवा-व्युत्पन्न वर्ग के सदस्य चर के रूप में मौजूद है
वीरेन

2

जब मैं पोस्ट को हिट करता हूं (Config.getURL ("लॉगिन"), मान) एप्लिकेशन को थोड़ी देर के लिए विराम लगता है (अजीब लगता है - एक सेवा के पीछे विचार यह था कि यह एक अलग धागे पर चलता है!)

इस मामले में एसिंक्टस्क का उपयोग करना बेहतर है, जो एक अलग धागे पर चलता है और परिणाम के पूरा होने पर वापस यूआई धागे पर लौटता है।


2

यहां एक और दृष्टिकोण है जो मूल रूप से आपको अनुरोधों के पूरे प्रबंधन के बारे में भूलने में मदद करता है। यह एक async कतार विधि और एक कॉल करने योग्य / कॉलबैक आधारित प्रतिक्रिया पर आधारित है। मुख्य लाभ यह है कि इस पद्धति का उपयोग करके आप पूरी प्रक्रिया (अनुरोध, प्राप्त करें और पार्स प्रतिक्रिया, sabe से db) को आपके लिए पूरी तरह से पारदर्शी बनाने में सक्षम होंगे। एक बार जब आप प्रतिक्रिया कोड प्राप्त कर लेते हैं तो काम पहले से ही पूरा हो जाता है। उसके बाद आपको बस अपने db पर कॉल करने की आवश्यकता है और आप कर रहे हैं। यह आपकी गतिविधि के सक्रिय नहीं होने पर क्या होता है की समस्याग्रस्त के साथ मदद करता है। यहाँ क्या होगा आप अपने सभी डेटा को अपने स्थानीय डेटाबेस में सहेज लेंगे, लेकिन प्रतिक्रिया आपकी गतिविधि से संसाधित नहीं होगी, यही आदर्श तरीका है।

मैंने यहाँ एक सामान्य दृष्टिकोण के बारे में लिखा है http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

मैं आगामी पोस्टों में विशिष्ट नमूना कोड डालूंगा। आशा है कि यह मदद करता है, दृष्टिकोण को साझा करने और संभावित संदेहों या मुद्दों को सुलझाने के लिए मुझसे संपर्क करने में संकोच न करें।


डेड लिंक। डोमेन समाप्त हो गया है।
jk7

1

रॉबी एक महान जवाब प्रदान करता है, हालांकि मैं देख सकता हूं कि आप अभी भी अधिक जानकारी की तलाश कर रहे हैं। मैंने REST एपी को आसान BUT गलत तरीके से कॉल किया। यह Google I / O वीडियो देखने तक नहीं था, जो मुझे समझ में आया कि मैं कहाँ गलत था। यह एक HttpUrlConnection get / put call के साथ एक AsyncTask को एक साथ रखने के रूप में सरल नहीं है।


डेड लिंक। यहाँ अपडेट किया गया है - Google I / O 2010 - Android REST क्लाइंट एप्लिकेशन
zim
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.