অ্যান্ড্রয়েডে অ্যাসিঙ্কটাস্ক এবং ত্রুটি পরিচালনা করে


147

আমি ব্যবহার থেকে আমার কোড রূপান্তর করছি Handlerকরার AsyncTask। পরেরটি এটি যা করে তাতে দুর্দান্ত - অ্যাসিক্রোনাস আপডেট এবং মূল ইউআই থ্রেডের ফলাফল পরিচালনা করা। আমার কাছে অস্পষ্টতাটি হ'ল যদি কোনও কিছু বায়বীয় হয় তবে কীভাবে ব্যতিক্রমগুলি পরিচালনা করতে হয় AsyncTask#doInBackground

আমি যেভাবে এটি করি তা হ'ল একটি ত্রুটি হ্যান্ডলার থাকা এবং এটিতে বার্তা প্রেরণ। এটি সূক্ষ্মভাবে কাজ করে, তবে এটি কি "সঠিক" পদ্ধতির বা আরও ভাল বিকল্প আছে?

এছাড়াও আমি বুঝতে পারি যে আমি যদি ত্রুটি হ্যান্ডলারের একটি ক্রিয়াকলাপ ক্ষেত্র হিসাবে সংজ্ঞায়িত করি তবে এটি ইউআই থ্রেডে কার্যকর করা উচিত। যাইহোক, কখনও কখনও (খুব অপ্রত্যাশিতভাবে) আমি একটি ব্যতিক্রম পেয়ে যাব বলে যে কোডটি ট্রিগার করা হয়েছে Handler#handleMessageতা ভুল থ্রেডে কার্যকর করা হচ্ছে। Activity#onCreateপরিবর্তে আমি ত্রুটি হ্যান্ডলারটি আরম্ভ করব ? স্থাপন runOnUiThreadমধ্যে Handler#handleMessageঅপ্রয়োজনীয় বলে মনে হয় কিন্তু এটি খুব নির্ভরযোগ্যভাবে সঞ্চালন করে।


আপনি কেন আপনার কোড রূপান্তর করতে চান? একটি ভাল কারণ ছিল?
এইচজিবিবি

4
@ হারাল্ডো এটি একটি ভাল কোডিং অনুশীলন যা কমপক্ষে আমার কেমন
লাগে

উত্তর:


178

এটি সূক্ষ্মভাবে কাজ করে তবে এটি কি "সঠিক" পদ্ধতির এবং এর চেয়ে ভাল বিকল্প আছে?

আমি সম্মুখের রাখা Throwableবা Exceptionমধ্যে AsyncTaskউদাহরণস্বরূপ নিজেই এবং তারপর এটা দিয়ে কিছু করতে onPostExecute(), তাই আমার ত্রুটি পরিচালনা অন-স্ক্রীন একটি ডায়ালগ প্রদর্শন করার বিকল্প নেই।


8
উজ্জ্বল! হ্যান্ডলারের সাথে আর বানর
লাগবে

5
আমার কি এইভাবে থ্রোয়েবল বা ব্যতিক্রম ধরে রাখা উচিত? "আপনার নিজের অ্যাসিঙ্কটাস্ক সাবক্লাসে একটি উদাহরণ-পরিবর্তনশীল যুক্ত করুন যা আপনার ব্যাকগ্রাউন্ড প্রসেসিংয়ের ফলাফলকে ধারণ করবে" " আপনি যখন ব্যতিক্রম পান, এই পরিবর্তনশীলটিতে ব্যতিক্রম (বা অন্য কোনও ত্রুটি-স্ট্রিং / কোড) সংরক্ষণ করুন। অনপস্টএক্সেকিউট যখন ডাকা হয়, দেখুন এই ইনস্ট্যান্স-ভেরিয়েবলটি কিছু ত্রুটিতে সেট করা আছে কিনা। যদি তাই হয়, একটি ত্রুটির বার্তা প্রদর্শন "(ব্যবহারকারীর থেকে" "বস্টন রাস্তা। Groups.google.com/group/android-developers/browse_thread/thread/... )
ওয়ান ওয়ার্ল্ড

1
@ ওয়ান ওয়ার্ল্ড: হ্যাঁ, এটি ঠিক আছে।
কমন্সওয়্যার

2
হাই সিডাব্লু, আপনি কি আরও বিস্তারিতভাবে এটি করার পদ্ধতিটি দয়া করে ব্যাখ্যা করতে পারেন - সম্ভবত একটি সংক্ষিপ্ত কোড উদাহরণ সহ? অনেক ধন্যবাদ!!
ব্রুজার

18
@ ব্রুইজার: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/… এর AsyncTaskবর্ণনা অনুযায়ী আমি যে ধরণটি বর্ণনা করেছি তার একটি নীচে রয়েছে ।
কমন্সওয়্যার

140

একটি এসিএনসিআরসাল্ট অবজেক্ট তৈরি করুন (যা আপনি অন্যান্য প্রকল্পেও ব্যবহার করতে পারেন)

public class AsyncTaskResult<T> {
    private T result;
    private Exception error;

    public T getResult() {
        return result;
    }

    public Exception getError() {
        return error;
    }

    public AsyncTaskResult(T result) {
        super();
        this.result = result;
    }

    public AsyncTaskResult(Exception error) {
        super();
        this.error = error;
    }
}

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

নীচে একটি কার্যের একটি মকআপ দেওয়া হয়েছে যা ওয়েব সার্ভার থেকে একটি জেএসওএন প্রতিক্রিয়া পায়।

AsyncTask<Object,String,AsyncTaskResult<JSONObject>> jsonLoader = new AsyncTask<Object, String, AsyncTaskResult<JSONObject>>() {

        @Override
        protected AsyncTaskResult<JSONObject> doInBackground(
                Object... params) {
            try {
                // get your JSONObject from the server
                return new AsyncTaskResult<JSONObject>(your json object);
            } catch ( Exception anyError) {
                return new AsyncTaskResult<JSONObject>(anyError);
            }
        }

        protected void onPostExecute(AsyncTaskResult<JSONObject> result) {
            if ( result.getError() != null ) {
                // error handling here
            }  else if ( isCancelled()) {
                // cancel handling here
            } else {

                JSONObject realResult = result.getResult();
                // result handling here
            }
        };

    }

1
আমি এটা পছন্দ করি. ভাল এনক্যাপসুলেশন। যেহেতু এটি আসল উত্তরের একটি প্যারাফ্রেজ উত্তর থেকে যায় তবে এটি অবশ্যই একটি
দাবির দাবি রাখে

জেনারিক্স কতটা কার্যকর হতে পারে এটি এটির একটি দুর্দান্ত প্রদর্শন। জটিলতার দিক থেকে এটি একটি অদ্ভুত গন্ধ ছোঁড়াচ্ছে, তবে আমি সত্যিই স্পষ্ট করে বলতে পারি এমনভাবে নয়।
num1

4
নিস ধারণা, শুধু একটা প্রশ্ন: কেন আপনি কল super()মধ্যে AsyncTaskResultযখন বর্গ কিছু প্রসারিত করে না?
অযথা

7
"কোনও ক্ষতি নেই" - রিডানড্যান্ট কোড পাঠযোগ্যতা এবং রক্ষণাবেক্ষণের জন্য সর্বদা ক্ষতিকারক। ওখান থেকে বেরিয়ে যাও! :)
অনর্থক

2
সমাধানটি সত্যিই পছন্দ হয়েছে ... এটি নিয়ে ভাবতে আসতে - সি # ছেলেরা সি # তেমন পটভূমি টাস্ক নেটিভ বাস্তবায়নে ঠিক একই পদ্ধতি ব্যবহার করেছিল ...
ভোভা

11

আমি যখন ব্যতিক্রমগুলি AsyncTaskসঠিকভাবে পরিচালনা করার প্রয়োজন অনুভব করি তখন আমি এটিকে সুপার ক্লাস হিসাবে ব্যবহার করি:

public abstract class ExceptionAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {

    private Exception exception=null;
    private Params[] params;

    @Override
    final protected Result doInBackground(Params... params) {
        try {
            this.params = params; 
            return doInBackground();
        }
        catch (Exception e) {
            exception = e;
            return null;
        }
    }

    abstract protected Result doInBackground() throws Exception;

    @Override
    final protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        onPostExecute(exception, result);
    }

    abstract protected void onPostExecute(Exception exception, Result result);

    public Params[] getParams() {
        return params;
    }

}

সাধারণ হিসাবে, আপনি doInBackgroundব্যাকগ্রাউন্ডের কাজ করতে আপনার সাবক্লাসে ওভাররাইড করেন, যেখানে খুশি খুশি ব্যতিক্রমগুলি যেখানে প্রয়োজন সেখানে নিক্ষেপ করুন। তারপরে আপনাকে প্রয়োগ করতে বাধ্য করা হয় onPostExecute(কারণ এটি বিমূর্ত) এবং এটি আপনাকে Exceptionপ্যারামিটার হিসাবে পাস করা সমস্ত ধরণের হ্যান্ডেল করার জন্য আলতো করে স্মরণ করিয়ে দেয় । বেশিরভাগ ক্ষেত্রে, ব্যতিক্রমগুলি কিছু ধরণের ইউআই আউটপুট নিয়ে যায়, তাই onPostExecuteএটি করার উপযুক্ত জায়গা।


1
ওহ, কেন কেবল paramsসামনের দিকে অগ্রসর হয় না, তাই এটি মূল এবং মাইগ্রেশন করা আরও সহজরূপ?
TWiStErRob

@TWiStErRob এই ধারণাটি নিয়ে কোনও ভুল নেই। আমি অনুমান করি এটি ব্যক্তিগত পছন্দের বিষয়, কারণ আমি প্যারাম ব্যবহার না করি। আমি পছন্দ new Task("Param").execute()উপর new Task().execute("Param")
সুলাই

5

আপনি যদি রবোগুইস ফ্রেমওয়ার্কটি ব্যবহার করতে চান যা আপনাকে অন্যান্য উপকারগুলি এনেছে আপনি রবোএসিঙ্কটাস্ক চেষ্টা করতে পারেন যার অতিরিক্ত কলব্যাক অন এক্সেক্সশন () রয়েছে। সত্যিকারের ভাল কাজ করে এবং আমি এটি ব্যবহার করি। http://code.google.com/p/roboguice/wiki/RoboAsyncTask


এই আপনার অভিজ্ঞতা কি? বেশ স্থিতিশীল?
নিককনডসন

কি RoboGuiceএখনও জীবিত? দেখে মনে হচ্ছে ২০১২ সাল থেকে আপডেট হয়নি?
দিমিত্রি কে

না, রবোগুইস মারা গেছে এবং হ্রাস পেয়েছে। ডাগার 2 হ'ল প্রস্তাবিত প্রতিস্থাপন, তবে এটি কেবল একটি হাড় ডিআই লাইব্রেরি।
আভি চেরি

3

সাফল্য এবং ব্যর্থতার জন্য কলব্যাকগুলি সংজ্ঞায়িত করে এমন একটি ইন্টারফেস দিয়ে আমি আমার নিজের অ্যাসিঙ্কটাস্ক সাবক্লাস তৈরি করেছি। সুতরাং যদি আপনার অ্যাসিঙ্কটাস্কে কোনও ব্যতিক্রম ছুঁড়ে ফেলা হয় তবে অনফিলার ফাংশনটি ব্যতিক্রমটি পাস হয়ে যায়, অন্যথায় অনস্যাক্সেস কলব্যাক আপনার ফলাফলটি পাস করে। অ্যান্ড্রয়েডের কেন আরও ভাল কিছু উপলব্ধ নেই তা আমার বাইরে।

public class SafeAsyncTask<inBackgroundType, progressType, resultType>
extends AsyncTask<inBackgroundType, progressType, resultType>  {
    protected Exception cancelledForEx = null;
    protected SafeAsyncTaskInterface callbackInterface;

    public interface SafeAsyncTaskInterface <cbInBackgroundType, cbResultType> {
        public Object backgroundTask(cbInBackgroundType[] params) throws Exception;
        public void onCancel(cbResultType result);
        public void onFailure(Exception ex);
        public void onSuccess(cbResultType result);
    }

    @Override
    protected void onPreExecute() {
        this.callbackInterface = (SafeAsyncTaskInterface) this;
    }

    @Override
    protected resultType doInBackground(inBackgroundType... params) {
        try {
            return (resultType) this.callbackInterface.backgroundTask(params);
        } catch (Exception ex) {
            this.cancelledForEx = ex;
            this.cancel(false);
            return null;
        }
    }

    @Override
    protected void onCancelled(resultType result) {
        if(this.cancelledForEx != null) {
            this.callbackInterface.onFailure(this.cancelledForEx);
        } else {
            this.callbackInterface.onCancel(result);
        }
    }

    @Override
    protected void onPostExecute(resultType result) {
        this.callbackInterface.onSuccess(result);
    }
}

3

ক্যাগায়ে কালানের সমাধানের আরও বিস্তৃত সমাধান নীচে দেখানো হয়েছে:

AsyncTaskResult

public class AsyncTaskResult<T> 
{
    private T result;
    private Exception error;

    public T getResult() 
    {
        return result;
    }

    public Exception getError() 
    {
        return error;
    }

    public AsyncTaskResult(T result) 
    {
        super();
        this.result = result;
    }

    public AsyncTaskResult(Exception error) {
        super();
        this.error = error;
    }
}

ExceptionHandlingAsyncTask

public abstract class ExceptionHandlingAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, AsyncTaskResult<Result>>
{
    private Context context;

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

    public Context getContext()
    {
        return context;
    }

    @Override
    protected AsyncTaskResult<Result> doInBackground(Params... params)
    {
        try
        {
            return new AsyncTaskResult<Result>(doInBackground2(params));
        }
        catch (Exception e)
        {
            return new AsyncTaskResult<Result>(e);
        }
    }

    @Override
    protected void onPostExecute(AsyncTaskResult<Result> result)
    {
        if (result.getError() != null)
        {
            onPostException(result.getError());
        }
        else
        {
            onPostExecute2(result.getResult());
        }
        super.onPostExecute(result);
    }

    protected abstract Result doInBackground2(Params... params);

    protected abstract void onPostExecute2(Result result);

    protected void onPostException(Exception exception)
    {
                        new AlertDialog.Builder(context).setTitle(R.string.dialog_title_generic_error).setMessage(exception.getMessage())
                .setIcon(android.R.drawable.ic_dialog_alert).setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener()
                {
                    public void onClick(DialogInterface dialog, int which)
                    {
                        //Nothing to do
                    }
                }).show();
    }
}

উদাহরণ টাস্ক

public class ExampleTask extends ExceptionHandlingAsyncTask<String, Void, Result>
{
    private ProgressDialog  dialog;

    public ExampleTask(Context ctx)
    {
        super(ctx);
        dialog = new ProgressDialog(ctx);
    }

    @Override
    protected void onPreExecute()
    {
        dialog.setMessage(getResources().getString(R.string.dialog_logging_in));
        dialog.show();
    }

    @Override
    protected Result doInBackground2(String... params)
    {
        return new Result();
    }

    @Override
    protected void onPostExecute2(Result result)
    {
        if (dialog.isShowing())
            dialog.dismiss();
        //handle result
    }

    @Override
    protected void onPostException(Exception exception)
    {
        if (dialog.isShowing())
            dialog.dismiss();
        super.onPostException(exception);
    }
}

আমি myActivity.getApplicationContext () হিসেবে getResources পেয়েছিলাম () মেথড getResources ()।
স্টিফেন

2

এই সাধারণ শ্রেণিটি আপনাকে সহায়তা করতে পারে

public abstract class ExceptionAsyncTask<Param, Progress, Result, Except extends Throwable> extends AsyncTask<Param, Progress, Result> {
    private Except thrown;

    @SuppressWarnings("unchecked")
    @Override
    /**
     * Do not override this method, override doInBackgroundWithException instead
     */
    protected Result doInBackground(Param... params) {
        Result res = null;
        try {
            res = doInBackgroundWithException(params);
        } catch (Throwable e) {
            thrown = (Except) e;
        }
        return res;
    }

    protected abstract Result doInBackgroundWithException(Param... params) throws Except;

    @Override
    /**
     * Don not override this method, override void onPostExecute(Result result, Except exception) instead
     */
    protected void onPostExecute(Result result) {
        onPostExecute(result, thrown);
        super.onPostExecute(result);
    }

    protected abstract void onPostExecute(Result result, Except exception);
}

2

পরিবর্তনীয় সদস্য ভাগ করে নেওয়ার উপর নির্ভর করে না এমন আরেকটি উপায় হ'ল বাতিল ব্যবহার।

এটি অ্যান্ড্রয়েড ডক্স থেকে:

সর্বজনীন চূড়ান্ত বুলিয়ান বাতিল (বুলিয়ান মেইনটারপটআইফরানিং)

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

এই পদ্ধতিতে কল করার ফলে ডিনব্যাকগ্রাউন্ড (অবজেক্ট []) ফেরতের পরে অনিয়ন্ত্রিত (অবজেক্ট) ইউআই থ্রেডে ডাকা হবে। এই পদ্ধতিতে কল করা গ্যারান্টি দেয় যে onPostExecute (অবজেক্ট) কখনই চাওয়া হয় না। এই পদ্ধতিটি চালিত করার পরে, কাজটি যত তাড়াতাড়ি সম্ভব শেষ করতে আপনার নিয়মিত doInBackground (অবজেক্ট []) থেকে isCancelled () দ্বারা ফিরে আসা মানটি পরীক্ষা করা উচিত।

সুতরাং আপনি ক্যাচ স্টেটমেন্টে ক্যান্সেল কল করতে পারেন এবং নিশ্চিত হয়ে উঠতে পারেন যে পোস্টপ্যাকসেকিউটটি কখনও কল হয় না, তবে পরিবর্তে onCancelled ইউআই থ্রেডে আহ্বান করা হয়। সুতরাং আপনি ত্রুটি বার্তা প্রদর্শন করতে পারেন।


আপনি ত্রুটি বার্তাটি সঠিকভাবে প্রদর্শন করতে পারবেন না, কারণ আপনি সমস্যাটি জানেন না (ব্যতিক্রম), আপনার এখনও একটি অ্যাসিঙ্কটাসকরেজাল্ট ধরতে হবে এবং ফিরে আসতে হবে। এছাড়াও একটি ব্যবহারকারী বাতিল করা ত্রুটি নয়, এটি একটি প্রত্যাশিত ইন্টারঅ্যাকশন: আপনি কীভাবে এইগুলির মধ্যে পার্থক্য করবেন?
TWiStErRob

cancel(boolean)onCancelled()শুরু থেকে অস্তিত্বের কল হিসাবে ফলস্বরূপ , তবে onCancelled(Result)এপিআই 11-এ যুক্ত হয়েছিল
TWiStErRob

0

আসলে, অ্যাসিঙ্কটাস ফিউচারটাস্ক এবং এক্সিকিউটার ব্যবহার করে, ফিউচারটাস্ক ব্যতিক্রম-শৃঙ্খলা সমর্থন করে প্রথমে একটি সহায়ক শ্রেণি সংজ্ঞায়িত করা যাক

public static class AsyncFutureTask<T> extends FutureTask<T> {

    public AsyncFutureTask(@NonNull Callable<T> callable) {
        super(callable);
    }

    public AsyncFutureTask<T> execute(@NonNull Executor executor) {
        executor.execute(this);
        return this;
    }

    public AsyncFutureTask<T> execute() {
        return execute(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    @Override
    protected void done() {
        super.done();
        //work done, complete or abort or any exception happen
    }
}

দ্বিতীয়ত, ব্যবহার করা যাক

    try {
        Log.d(TAG, new AsyncFutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //throw Exception in worker thread
                throw new Exception("TEST");
            }
        }).execute().get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        //catch the exception throw by worker thread in main thread
        e.printStackTrace();
    }

-2

ব্যক্তিগতভাবে, আমি এই পদ্ধতির ব্যবহার করব। আপনি কেবলমাত্র ব্যতিক্রমগুলি ধরতে পারেন এবং যদি আপনার তথ্যের প্রয়োজন হয় তবে স্ট্যাক ট্রেসটি মুদ্রণ করতে পারেন।

ব্যাকগ্রাউন্ডে আপনার টাস্কটিকে একটি বুলিয়ান মান ফেরত দিন।

এটি এর মতো:

    @Override
                protected Boolean doInBackground(String... params) {
                    return readXmlFromWeb(params[0]);
         }

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}

-2

আর একটি সম্ভাবনা হ'ল Objectরিটার্ন টাইপ হিসাবে ব্যবহার করা এবং onPostExecute()অবজেক্ট টাইপের জন্য পরীক্ষা করা। এটি সংক্ষিপ্ত।

class MyAsyncTask extends AsyncTask<MyInObject, Void, Object> {

    @Override
    protected AsyncTaskResult<JSONObject> doInBackground(MyInObject... myInObjects) {
        try {
            MyOutObject result;
            // ... do something that produces the result
            return result;
        } catch (Exception e) {
            return e;
        }
    }

    protected void onPostExecute(AsyncTaskResult<JSONObject> outcome) {
        if (outcome instanceof MyOutObject) {
            MyOutObject result = (MyOutObject) outcome;
            // use the result
        } else if (outcome instanceof Exception) {
            Exception e = (Exception) outcome;
            // show error message
        } else throw new IllegalStateException();
    }
}

1
সম্পূর্ণ অপ্রাসঙ্গিক
দিনু

-2

আপনি যদি সঠিক ব্যতিক্রমটি জানেন তবে আপনি কল করতে পারেন

Exception e = null;

publishProgress(int ...);

উদাহরণ:

@Override
protected Object doInBackground(final String... params) {

    // TODO Auto-generated method stub
    try {
        return mClient.call(params[0], params[1]);
    } catch(final XMLRPCException e) {

        // TODO Auto-generated catch block
        this.e = e;
        publishProgress(0);
        return null;
    }
}

এবং "অনপ্রযুক্তি আপডেট" এ যান এবং ফলোয়িং করুন

@Override
protected void onProgressUpdate(final Integer... values) {

    // TODO Auto-generated method stub
    super.onProgressUpdate(values);
    mDialog.dismiss();
    OptionPane.showMessage(mActivity, "Connection error", e.getMessage());
}

এটি শুধুমাত্র কিছু ক্ষেত্রে সহায়ক হবে। এছাড়াও আপনি একটি Global Exceptionপরিবর্তনশীল রাখতে পারেন এবং ব্যতিক্রম অ্যাক্সেস করতে পারেন ।


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