<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های مهدی ایران نژاد</title>
        <link>https://virgool.io/feed/@mahdiirannezhad</link>
        <description>برنامه نویس و توسعه دهنده بازی های اندرویدی در Unity، طراح UI</description>
        <language>fa</language>
        <pubDate>2026-06-16 13:17:28</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/392252/avatar/U8D9xz.jpeg?height=120&amp;width=120</url>
            <title>مهدی ایران نژاد</title>
            <link>https://virgool.io/@mahdiirannezhad</link>
        </image>

                    <item>
                <title>جنگ من و Unity با اندروید بر سر یک دکمه کوچک!</title>
                <link>https://virgool.io/@mahdiirannezhad/unity-android-sdk36-predictive-back-issue-bdmufjrrxmnz</link>
                <description>مقدمه شروع:توی این مقاله قراره با زبان طنز-فنی، یه سفر کوتاه اما پر از پیچ و خم رو با هم طی کنیم؛ سفری به دل ماجرایی عجیب غریب که سر راه توسعه بازیمون توی یونیتی سبز شد و کلی اعصاب خوردی، آزمون و خطا و البته تجربه با خودش آورد. راه حل نهایی من همون جاییه که با «قهرمان پوشالی‌مون» یعنی Custom Activity خداحافظی کردم، ولی اگه دلتون میخواد بدونید چی شد که کار به اینجا رسید، پیشنهاد میکنم مقاله رو از اول بخونید.قول میدم یه لبخند کوچولو هم شده گوشه لبتون سبز بشه ;-)آپدیتی که همه چیز داشت، جز دکمه Back سالمهمه چیز آماده بود. یه بروزرسانی اساسی برای بازی داشتیم، حدود 2 ماه و نیم داشتیم روی پروژه کار میکردیم، همه چیز تست شده، همه صحنه ها سالم، حتی QA هم چراغ سبز داده بود؛ فقط مونده بود دکمه معروف &quot;Release&quot; رو بزنیم و با خیال راحت آپدیت جدید رو بفرستیم روی گوگل پلی. اما... همیشه اون لحظه آخر یه چیزی میاد میزنه برجکمون رو خراب میکنه، چرا؟ چون دنیا که رو یه پاش نمیچرخه...گوگل پلی اومد گفت: &quot;اگه میخوای آپدیت جدید بدی، باید Target SDK رو حداقل بذاری روی 35.&quot; ما هم گفتیم باشه ولی با یه ذره شیطنت! گذاشتیمش روی 36 که بگیم آره ما خیلی آپدیتیم داداش😎اینجا بود که کم کم آسمون برامون ابری شد. چون با بالا رفتن Target SDK به 36، دیگه Min SDK 22 جواب نمیداد و Gradle همون اول بیلد میگفت: &quot;من با 22 نمیسازم!&quot; پس مجبور شدیم Min SDK رو هم ببریم بالا، شد 23.تا اینجای کار گفتیم خب، یه مقدار هزینه برای آپدیت میدیم، ولی حداقل خیالمون راحته. اما نه... سه هفته بعد، یه سری گزارش عجیب رسید از سمت کاربران اندروید ۱۶. میگفتن وقتی دکمه Back رو که میزنن، بازی نه Pause میشه، نه به صفحه قبل برمیگرده، بلکه یهو بسته میشه یا میره توی Background!انگار که دکمه Back شده بود دکمه &quot;برو بیرون و پشت سرتو هم نگاه نکن!&quot; 😐با اولین تست فنی روی Android 16، دکمه Back زده بود زیر همچی. به جای اینکه بازی برگرده به منو یا صفحه قبل، کل اپ میپرید بیرون یا Minimize میشد. حتی Unity هم انگار اصلاً خبر نداشت که دکمه Back فشار داده شده!جالبتر اینکه این مشکل فقط روی اندروید ۱۶ اتفاق می افتاد و بازی روی بقیه اندروید های قدیمی، کاملاً درست کار میکرد. انگاری که Android 16 مثل بچه وسط خانواده قهر کرده بود. 😒Unity روی اندروید 16 اصلا Input.GetKeyDown(KeyCode.Escape) رو تشخیص نمیداد و هیچ واکنشی براش نداشت.اما ته ماجرا چی بود؟ گوگل از Android 13 به بعد اومده یه قابلیتی اضافه کرده به اسم Predictive Back Gesture، قابلیتی که توی Android 16 شدیدتر اعمال میشه؛ این یعنی وقتی کاربر دکمه Back رو میزنه، سیستم قبل از انجام هر کاری یه بررسی میکنه ببینه دقیقاً باید چه رفتاری نشون بده: عقب بره؟ جلو بره؟ App رو ببنده؟ هیچی نگه؟ ولی خب اینجا Unity مثل همیشه سرش تو لاک خودش بود، اصلاً حواسش نبود این دکمه الان شده دکمه همه فن حریف! نتیجه؟ کاربر دکمه رو میزنه، سیستم میگه &quot;خب این برنامه هیچی نگفت، پس من خودم تصمیم میگیرم&quot;، و در یک عملیات انتحاری میزنه بازی رو میبنده بدون اینکه Unity فرصت داشته باشه یه خداحافظی خشک و خالی کنه.مسیر پرماجرای نجات دکمه Backاولین واکنش ما بعد از کشف مشکل دکمه Back این بود که بگیم:«خب نکنه Unity خودش یه تنظیماتی داره برای اینجور مواقع؟»با یک سرچ ساده رفتیم سراغ آشناترین و در دسترس ترین گزینه:Input.backButtonLeavesApp = false;این خط همیشه یه حس اطمینان خاصی میداد. معنیش هم ساده بود:«یونیتی جون، جان جدت لطفاً دکمه Back رو دست خودت نگه دار، نذار سیستم بزنه بازی رو minimize کنه یا ببنده.»ولی ظاهراً این خط کد توی اندروید 16 دیگه اون اعتباری که قبلاً داشت رو از دست داده بود.انگار اندروید جدید رسماً اعلام کرده بود که:«من دیگه به Unity گوش نمیدم. تو دکمه بک رو بزنی هم من بازی رو میفرستم پشت صحنه، حالا هرچی میخوای بگو.»نتیجه؟ هیچگونه تغییر خاصی در رفتار Back مشاهده نشد. حتی در لاگ ها هم اثری از پردازش توسط یونیتی نبود.در واقع، سیستم عامل مستقیماً کنترل رو در دست گرفته بود و Unity عملاً کنار گذاشته شده بود.در نهایت، این روش رو گذاشتیم کنار. چون هر چقدر هم که nostalgically دلبسته اش بودیم، توی اندروید 16 دیگه هیچ کاری برامون نمیکرد.Back در حال فرار 😂وقتی گفتیم «خودمون Back رو هندل کنیم!»بعد از اینکه فهمیدیم یونیتی با Input.backButtonLeavesApp = false هیچ واکنشی به اندروید 16 نشون نمیده، رفتیم سراغ راه های عمیق تر؛ اینجا بود که تصمیم گرفتیم خودمون دست به کار بشیم و یک Activity سفارشی بسازیم تا دقیقاً همونطور که ما میخوایم، با دکمه Back رفتار کنه.در اصل، یونیتی یه Activity اصلی به داره به اسم UnityPlayerActivity (و اگه فایربیس استفاده کنی، MessagingUnityPlayerActivity رو استفاده میکنه که از همون ارث بری شده).داخل یونیتی های ورژن 2023.1 و جدیدتر، اکتیویتی اصلیUnityPlayerGameActivityاست.ایده ما این بود که بیایم یه اکتیویتی کلاس جدید درست کنیم که ازMessagingUnityPlayerActivity ارث بری کنه (چون ما داخل پروژه فایربیس رو داشتیم) و توی اون، متدهای مربوط به ایونت Back مثل onBackPressedوOnBackInvokedCallbackرو خودمون مدیریت کنیم.به زبان ساده تر، گفتیم:«به جای اینکه به یونیتی یا اندروید بگیم Back رو مدیریت کن، بیایم خودمون کار رو دست بگیریم.»کد نمونه اولیه چیزی شبیه این بود:Assets/Plugins/Android/CustomUnityPlayerActivity.java:/// Java class is generated and saved in the `Assets/Plugins/Android/CustomUnityPlayerActivity.java`
/// Replace `[dot]` with `.`

package com[dot]yourcompany[dot]yourgame;

import android[dot]os[dot]Build;
import android[dot]os[dot]Bundle;
import android[dot]view[dot]KeyEvent;

import com[dot]google[dot]firebase[dot]MessagingUnityPlayerActivity;
import com[dot]unity3d[dot]player[dot]UnityPlayer;

import android[dot]window[dot]OnBackInvokedCallback;
import android[dot]window[dot]OnBackInvokedDispatcher;

public class CustomUnityPlayerActivity extends MessagingUnityPlayerActivity {
    private OnBackInvokedCallback predictiveBackCallback;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.TIRAMISU) {
            predictiveBackCallback = this::onBackPressed;
            getOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, predictiveBackCallback);
        }
    }

    @Override
    protected void onDestroy() {
        if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.TIRAMISU &amp;&amp; predictiveBackCallback != null) {
            getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(predictiveBackCallback);
        }

        super.onDestroy();
    }

    @Override
    public void onBackPressed() {
        UnityPlayer.currentActivity.runOnUiThread(() -&gt; {
            UnityPlayer.currentActivity.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK));
            UnityPlayer.currentActivity.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK));
        });
    }
}Assets/Plugins/Android/AndroidManifiest.java:/* Replace `[dot]` with `.` */
 &lt;application ... other tags ...&gt;
    &lt;activity android:name=&quot;com[dot]yourcompany[dot]yourgame[dot]CustomUnityPlayerActivity&quot; /* Changed this line to use CustomUnityPlayerActivity */
              android:enableOnBackInvokedCallback=&quot;true&quot; /* Added this line */
              ... other tags ... &gt;
....
....
....
    &lt;/activity&gt;
....
....
    &lt;meta-data android:name=&quot;unityplayer.ForwardNativeEventsToDalvik&quot; android:value=&quot;true&quot; /&gt; /* Added this line */
  &lt;/application&gt;این تغییرات اومده یه جورایی با سیستم عامل وارد مذاکره میشه! اونجوری که توی متد onCreate با یه OnBackInvokedCallback قرارداد میبنده و به اندروید میگه: &quot;داداش، اگه کاربری خواست BACK بزنه چه از طریق اون Predictive Back، چه از طریق سیستم های قدیمی در اندروید های قبل از 13، اول منو صدا بزن، خودم میدونم باید چیکارش کنم.&quot;و ما هم وقتی کلاسمون صدا زده میشه، خیلی شیک و مجلسی، یک رویداد BACK تقلبی و دستی برای Unity میفرستیم تا بازی طبق منطق خودش رفتار کنه، نه اینکه یهو minimize بشه یا ببنده بره قاقا قیلی. 😂حالا متد onBackPressed هم که از قدیم اونجا بود، برای نسخه های پایین تر از 13 همچنان باید آستین بالا بزنه. چون ما عملاً کنترل Back رو از چنگ سیستم عامل کشیدیم بیرون، خودمون باید با دوتا dispatchKeyEvent (یکی DOWN و یکی UP) وانمود کنیم که کاربر دکمه BACK رو فشار داده، تا Unity بتونه طبق روال طبیعی خودش باهاش برخورد کنه.در onDestroy هم اگه CallBack قبلاً ثبت شده باشه، خیلی مؤدبانه unregisterش میکنیم که بعداً توی حافظه ول چرخی نکنه یا موقع خروج به Dispatcher گیر بده. (به قول معروف، خانه تکانی قبل از رفتن.)خلاصه اینکه CustomUnityPlayerActivity دقیقاً یه جور واسطه گر حرفه ایه: نه میذاره اندروید یه تنه تصمیم بگیره، نه اجازه میده Unity بی خبر بمونه. همه چیز رو با دیپلماسی دقیق بین این دو تا غول بی سر و ته مدیریت میکنه، طوری که هم سیستم راضی باشه، هم بازی، گور پدر ناراضی.همه چیز خوب بود تا اینکه اندروید 10 رسید...بالاخره بعد از کلی تست و ور رفتن با اکتیویتی سفارشی، دکمه Back مثل ساعت داشت کار میکرد نه تنها روی اندروید 16 حتی قبلی ها هم دیگه مثل بچه آدم حرف گوش میکردن. بازی خوشگل اجرا میشد، Back هم تابع قوانین Unity شده بود و دیگه هم کسی بیرون نمی پرید!تا اینکه... اندروید 10 (همون API 29) و نسخه های قدیمی ترش یهویی اومدن وسط و گفتن:&quot;آقا ببخشید، mUnityPlayer کیه دقیقاً؟ ما همچین چیزی نمیشناسیم! مامااااان....&quot;AndroidJavaException: java.lang.NoSuchFieldError: mUnityPlayerو بوووووم! سیستم به کل قهر کرد و بازی روی اندروید 10 فقط با نگاه به اسم mUnityPlayer سکته میکرد.واکنش من این لحظه: «نه، نه، این حقیقت ندارد، این حقیقت ندااارررد...😭😭»پس از کلی بررسی و کاوش متوجه شدم که این خطا فقط روی اندروید 10 و پایین تر رخ میده و باعث میشه کلاس های مربوط به Java Native Interface در Unity از جملهAndroidJavaObject,AndroidJavaClass و AndroidJNI عملاً از کار بیفتن و تمامی پلاگین هایی که داخل یونیتی قصد کارکردن با سیستم عامل و Java رو داشتن مثل Notification, Firebase و موارد مشابه با اختلال جدی مواجه بشن.نکته مرموز اینجا بود که متغیر mUnityPlayer در اصل از نوع protected بود و تو نسخه های بالاتر براحتی از طریق reflection قابل دستیابی بود. اما در اندروید 10، ساختار reflection کمی سنتی تر و سختگیرتر بود. در واقع، از اندروید 11 به بعد reflection یه سری قابلیت ها و تغییرات جدید دریافت کرده بود که باعث میشد دسترسی به فیلدهای protected راحت تر باشه، ولی اندروید 10 هنوز اون آپدیتها رو نداشت.نتیجه چی بود؟ ارور NoSuchFieldError بی مقدمه وسط اجرا می پرید و کل ارتباطات جاوا-یونیتی رو منفجر میکرد.ماجراجویی هایم برای نجات از خطای mUnityPlayerبرای نجات از این وضعیت، دست به یه سری عملیات زدم:پروگارد را التماس کردم:یه عالمه keep- نوشتم بلکه جلوی حذف چیزایی که حتی نمیدونم اصلاً وجود دارن یا نه رو بگیرم.Reflection درمانی:نشستم با Java Reflection بازی کردم که شاید بتونم با getDeclaredField(&quot;mUnityPlayer&quot;) نجاتش بدم. نتیجه چی بود؟ یه ارور محکم تو صورتم! از اوناش که اگه مزاحم یه دختر بشی سیلی اش رو میخوری! 😂Copy/Paste درمانی:گفتم بیام به جای ارث بری از MessagingUnityPlayerActivity، کل کدهاش رو خودم مستقیم توی کلاس CustomUnityPlayerActivity پیاده کنم و از UnityPlayerActivity ارث بری کنم. فکر میکردم با این حرکت، کنترل کامل دست خودمه. بازم نتیجه چی بود؟ خب میدونی، از بیرون قشنگ بود، ولی همچنان اندروید 10 باهام لج بود.مهندسی معکوس برای متغیر mUnityPlayer:تلاش کردم خودم یه متغیر public به اسمmUnityPlayerتعریف کنم و به جای اصلیه قالب کنم ولی این بار باز هم سیستم عامل با چشم غره جوابم رو داد.سکوت و نادیده گرفتن:یه مرحله ای فقط نگاهش میکردم و وانمود میکردم ارور نیست. خب اینم جواب نداد ولی روحیه امو حفظ کرد.نتیجه ماجراجویینتیجه تمام این تلاش ها چی بود؟ فهمیدم ور رفتن با هر چیزی باعث میشه باهات ور رفته بشه و دسترسی به اعضای non-public روی اندرویدهای قدیمی به وسیله reflection مثل تلاش برای هک کردن یخچال با برنامه هواشناسیه.هر چقدر هم کد درست باشه، وقتی دسترسی مورد نظرت توی سیستم عامل اصلاً وجود نداره، فقط خودتو سرویس کردی.خداحافظ با قهرمان پوشالی، Custom Activityبعد از ساعت ها جنگ بی امان با انواع ارث بری، کالبک، پراکسی و reflection، بالاخره رسیدم به یه واقعیت تلخ ولی نجات بخش: گاهی بهترین کار، نکردنه. بالاخره پذیرفتم که شاید اصلاً نیازی به دست کاری Activity اصلی نیست!نه به خاطر کم آوردن زیر فشار خطاها، بلکه چون عملاً هر بار که سعی میکردم مسیرش رو ادامه بدم، یه جای کار میلنگید؛ یا یه باگ جدید ظاهر میشد، یا یه نسخه از اندروید ناراحت میشد، یا خود یونیتی قهر میکرد، یا سیستم عامل درخواست طلاق میداد؛ خلاصه، تصمیم گرفتم یه قدم عقب تر بردارم و محترمانه، پرچم سفید رو بالا ببرم و بگم:&quot;اکتیویتی سفارشی عزیز، ازت ممنونم، ولی دیگه ادامه این راه با من نیست...&quot;و در نهایت به برگشتم عقب و به چیزی فکر کردم که از اول باید میکردم:“چطور میتونم فقط و فقط دکمه BACK رو هندل کنم، بدون اینکه به اکتیویتی دست بزنم؟”و جواب خیلی ساده تر از اون چیزی بود که فکرشو میکردم:یه پلاگین جاوا با یه کلاس معمولی، بدون اکتیویتی، بدون ارث بری، بدون هیچ چیزی، فقط یک واسطه بین Android و Unity. یک راهکار راحت، مینیمال، و بدون دردسرهای reflection و پروگارد، سازگار با گوشیهای نوستالژیک حتی اندروید 6 و حتی بدون جنگ اعصاب!بازی در این مرحله تو همه نسخه ها از اندروید 6 گرفته تا 16 مثل ساعت کار کرد. نه Back عجله ای داشت، نه Unity قهر میکرد. همه چیز مثل یه گربه اهلی شده، سر جاش نشست و دقیقاً همون رفتاری رو کرد که انتظار داشتم.پس گاهی وقتا راه حل درست، همون ساده ترینشه... فقط باید از پیچیده بودن دست برداریم!پیاده سازی Back Handler بدون تغییر در اکتیویتیخب، ما تو این روش اصلاً دست به اکتیویتی نمیزنیم. به جاش یه کلاس Java میسازیم با اسم UnityBackHandlerکه به عنوان یه &quot;شنونده محترم برای دکمه Back&quot; استخدام شده.البته ناگفته نماند، کلاس CustomUnityPlayerActivity رو از پروژه حذف کردم و از همان activity پیش فرض و قبلی پروژه داخل AndroidManifiest استفاده کردم.Assets/Plugins/Android/UnityBackHandler.java:/// Java class is generated and saved in the `Assets/Plugins/Android/UnityBackHandler.java`
/// Replace `[dot]` with `.`

package com[dot]yourcompany[dot]yourgame;

import android[dot]app[dot]Activity;
import android[dot]os[dot]Build;
import android[dot]view[dot]KeyEvent;
import android[dot]view[dot]View;
import android[dot]window[dot]OnBackInvokedCallback;
import android[dot]window[dot]OnBackInvokedDispatcher;

import com[dot]unity3d[dot]player[dot]UnityPlayer;

public class UnityBackHandler {
    private static OnBackInvokedCallback predictiveBackCallback;
    private static boolean isInitialize = false;

    public static void Setup() {
        if (isInitialize)
            return;

        final Activity activity = UnityPlayer.currentActivity;

        activity.runOnUiThread(() -&gt; {
            if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.TIRAMISU) {
                predictiveBackCallback = BackHandler::dispatchUnityBack;
                activity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, predictiveBackCallback);
            }

            View decorView = activity.getWindow().getDecorView();
            decorView.setFocusableInTouchMode(true);
            decorView.requestFocus();
            decorView.setOnKeyListener((v, keyCode, event) -&gt; {
                if (keyCode == KeyEvent.KEYCODE_BACK &amp;&amp; event.getAction() == KeyEvent.ACTION_UP) {
                    dispatchUnityBack();
                    return true;
                }

                return false;
            });
        });

        isInitialize = true;
    }

    private static void dispatchUnityBack() {
        Activity activity = UnityPlayer.currentActivity;
        activity.runOnUiThread(() -&gt; {
            activity.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK));
            activity.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK));
        });
    }

    public static void Destroy() {
        if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.TIRAMISU &amp;&amp; predictiveBackCallback != null) {
                UnityPlayer.currentActivity.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(predictiveBackCallback);
        }
    }
}Assets/Plugins/Android/AndroidManifiest.java: &lt;application ... other tags ...&gt;
    &lt;activity android:name=&quot;XXXXXXXXX&quot; /* Use old activity like `UnityPlayerActivity` or `MessagingUnityPlayerActivity` depends on your project */
              android:enableOnBackInvokedCallback=&quot;true&quot; /* Added this line */
              ... other tags ... &gt;
....
....
....
    &lt;/activity&gt;
....
....
    &lt;meta-data android:name=&quot;unityplayer.ForwardNativeEventsToDalvik&quot; android:value=&quot;true&quot; /&gt; /* Added this line */
  &lt;/application&gt;تا اینجای کار تغییرات مورد نیاز در سطح Java روی پروژه اعمال شده، الان به یک فعال ساز نیاز داریم که کد کلاس UnityBackHandler رو در سطح Unity برای ما فعال کنه، پس یک کلاس یونیتی به اسم AndroidBackDispatcher داخل یونیتی میسازیم (با ارث بری از MonoBehaviour) و دستورات زیر را داخل آن قرار میدهم:using UnityEngine;

public class AndroidBackDispatcher : MonoBehaviour
{
    private void Awake()
    {
        if (Application.platform != RuntimePlatform.Android)
            Destroy(gameObject);

#if UNITY_ANDROID &amp;&amp; !UNITY_EDITOR
        using (var javaClass = new AndroidJavaClass(&quot;com[dot]yourcompany[dot]yourgame[dot]UnityBackHandler&quot;)) /// Replace `[dot]` with `.`
        {
            javaClass.CallStatic(&quot;Setup&quot;);
        }
#endif
    }

    private void OnApplicationQuit()
    {
#if UNITY_ANDROID &amp;&amp; !UNITY_EDITOR
        using (var javaClass = new AndroidJavaClass(&quot;com[dot]yourcompany[dot]yourgame[dot]UnityBackHandler&quot;)) /// Replace `[dot]` with `.`
        {
            javaClass.CallStatic(&quot;Destroy&quot;);
        }
#endif
    }
}فراموش نکنیم که این کلاس باید به یک آبجکت در اولین مرحله از اجرای بازی متصل شود تا عملیات به درستی اجرا شود. ضمنا برای پیشگیری از هرگونه مشکلات احتمالی خطوط زیر رو به انتهای فایل پروگارد پروژه نیز اضافه میکنیم:/// Replace `[dot]` with `.`
-keep class com[dot]yourcompany[dot]yourgame[dot]UnityBackHandler {
    public static void Setup();
    public static void Destroy();
}خب کار تمام شد، لعنت‌ گویان به گوگل، بریم سراغ ماجراجویی بعدیمون….منتظر نظرات و پیشنهادات شما هستم🌹🌹</description>
                <category>مهدی ایران نژاد</category>
                <author>مهدی ایران نژاد</author>
                <pubDate>Sun, 03 Aug 2025 22:06:32 +0330</pubDate>
            </item>
                    <item>
                <title>راه کار های مدیریت بهتر پروژه‌ها در Git</title>
                <link>https://virgool.io/@mahdiirannezhad/git-flow-vgeruscfhfh4</link>
                <description>پ.ن: این مقاله برای کسانی مناسب است که حداقل آشنایی لازم و تجربه کار با Git را داشته باشند. مهم ترین دغدغه مدیریت پروژه در GITهمانطور که مطلع هستید، Git یک ابزار برای مدیریت تغییرات در یک پروژه است، به عنوان مثال در تیم های کوچک (۲ تا ۳ نفره)، استفاده از گیت به سادگی قابل انجام است و تا حدودی نیز کمک کننده است؛ حال اگر بخواهیم این مسئله را به پروژه های بلند مدت و با پیچیدگی و وابستگی های بالا یا به اجرای پروژه در تیم های بزرگتر تعمیم دهیم، آن وقت چه اتفاقی رخ خواهد داد؟اینجاست که اگر قوانین سختگیرانه‌ای در مورد &quot;چگونگی اعمال تغییرات روی خود Git&quot; نداشته باشیم؛ ممکن است در دراز مدت شاهد یک پروژه با Git بسیار بهم ریخته و اصطلاحا شلخته روبه‌رو شویم.چرا اینقدر پروژه‌ام در GIT بهم ریخته اس؟دلایل زیادی میتواند باعث بهم ریختگی پروژه شود؛ در ادامه چند مورد از دلایلی که طبق تجربه خودم، از عوامل تخریب نظم پروژه هستند را باهم بررسی میکنیم:طول عمر زیاد Branchهای پروژهعدم وجود مدیریت منسجم روی Merge شدن Branchهاعدم وجود رابطه کامل بین نرم افزار Project manager مانند Jira با نرم‌افزار کنترل نسخه مانند Gitعدم وجود قائده کامنت گذاری مشخصعدم وجود قائده مشخصی برای ایجاد Branchها و نام‌گذاری آنهاو دلایل دیگر...نمونه ای از مشکلات بهم ریختگی پروژهحال بیاید یک نمونه از پروژه های که از ابتدا بدون هیچ قانون و مدیریت خاصی روی Git تاکنون پیشرفت کرده را باهم بررسی کنیم؛ توجه کنید این پروژه یک پروژه موفق و پر بازده است، ما قرار است در بخش فنی و صرفا از نظر تکنیکال آن را مورد بررسی قرار دهیم.پ.ن: به علت حفظ اصل محرمانگی داده و جلوگیری از آشکار شدن مشخصات پروژه، قسمت هایی که شامل اطلاعات حساس هستند از داخل تصویر بالا حذف شده‌اند. در ادامه سعی کنید چند سوال خیلی مهم را با توجه به وضعیت پروژه در تصویر بالا پاسخ دهید:آخرین نسخه پایدار پروژه کجاست؟آخرین نسخه منتشر شده پروژه کجاست؟هر Branch مربوط به چه Task یا Bug است؟قاعده نام‌گذاری Branch ها چیست؟برای شروع تغییرات از کجا شروع کنیم؟مدیریت تغییرات روی Branch ها چطور انجام می‌شود؟تفاوت Branchهایی که عنوان master را یدک میکشند با یکدیگر چیست؟تفاوت پوشه Bug و Bugfix چیست؟تفاوت Branchهایی که در خارج از پوشه ها ساخته شده اند با مابقی Branchها چیست؟اگر توانستید تمام سوالات بالا را جواب دهید؛ این مقاله به درد شما نمیخورد پس آن را ببندید و از وجود چنین مقاله ای اظهار بی‌اطلاعی کنید چون شما در Levelهای بالاتری از من و ۹۹ درصد توسعه دهندگان جهان زندگی می‌کنید.خب راه کار مناسب برای جلوگیری از مشکل پیش آمده و شفاف کردن مسائل مطرح شده در سوالات بالا، چیست؟ اینجاست که ما با مفهومی به اسم Git Flow آشنا میشویم.قهرمان داستان ما، GIT FLOW!!!همانطور که در بحث مهندسی نرم افزار مفهومی تحت عنوان روش های مدیریت پروژه مانند Scrum, Agile, XP و موارد مشابه داریم، در بحث مدیریت Git نیز چنین مفاهیمی برای این مسئله داریم که شامل یک سری قوانین و اصول از پیش تعریف شده هستند که میتوانند بر اساس سیاست ها و ساختار هر تیم، شخصی‌سازی و مورد استفاده قرار گیرند؛ کلمه Git Flow در حقیقت اسم یکی از ابزار هایی است که Git برای مدیریتش در اختیار ما قرار می‌دهد.وظایف GIT FLOWاز مهمترین وظایفی گه ما از Git Flow انتظار داریم برای ما برآورده کند، میتوانیم به موارد زیر اشاره کنیم:مشخص کردن نقش هر عضو از تیم یا پروژه در Gitجلوگیری از بهم ریختگی های احتمالیبالا بردن ضریب اطمینان و سادگی کار با Gitمشخص کردن Branch ها با موضوعات مشخصتسریع مدیریت Branch ها و جلوگیری از بروز تداخلدسترسی هر چه سریعتر به نسخه های موردنظر در پروژهحفظ کردن تاریخچه تغییرات مشترک مرتبط با هر Taskدسته بندی Branch‌ها در GIT FLOWدر فرایند Git Flow تمامی Branchها به چند دسته‌بندی زیر تقسیم می‌شوند و ساخت یک Branch خارج از این دسته‌بندی باعث بروز مشکلات گفته شده می‌شود.ساختار موضوعی Branchهاخوشه Master: شامل آخرین نسخه پایدار منتشر شدهخوشه Develop: شامل آخرین نسخه پایدار توسعه یافتهپوشه Hotfix: شامل نسخه های پایدار برای آپدیت های اورژانسیپوشه Release: شامل نسخه های رسمی منتشر شدهپوشه Feature: شامل نسخه های پایدار دارای فقط یک ویژگی جدید یا بهبود یافتهپوشه Bugfix: شامل نسخه های پایدار عاری از مشکلات شناسایی شدهپوشه Upgrade: شامل نسخه های پایدار از تغییرات در ساختار فنی و هسته اصلی پروژهمشخصات خوشه Master، تجسمی از اعتماد و پایداریشامل آخرین نسخه Production یا منتشر شده از پروژه است.هیچ کسی از پروژه مجاز به اعمال حتی یک Commit روی این Branch نیست.تنها عملیات مجاز، عمل Merge از سایر Branchهای پایدار است.تنها مدیر پروژه مجاز به اعمال تغییرات یا Merge است.نسخه ها با Tag منحصر به فرد قابل شناسایی هستند.هر نسخه بلافاصله بعد از Merge روی Master، منتشر میشود.تنها Branch های Hotfix و Release مجاز به Merge شدن روی Master هستند.مشخصات خوشه Develop، دوست خوب من و توشامل آخرین نسخه Development یا آماده انتشار از پروژه است.همه اعضای پروژه فقط مجاز به عمل Commit روی Develop هستند.تنها مدیر پروژه مجاز به عمل Merge روی Develop است.تمام Branchها مجاز هستند روی Develop ادغام شوند.تغییرات Develop از Master, Hotfix یا Release شروع میشود.میزان پایداری آن همانند Master است و همیشه آخرین نسخه پروژه در این Branch آماده انتشار است.مشخصات پوشه Hotfix، اتاق عمل اورژانسیفرض کنید بعد از یک ماه از انتشار آخرین نسخه، متوجه یک مشکل بحرانی در پروژه می‌شوید و از طرفی هم در شرایطی نیستید که بخواهید آخرین نسخه توسعه یافته پروژه خود را منتشر کنید یا اصطلاحا آپدیت جدید داشته باشید؛ در چنین شرایطی باید از Master یک Sub-Branch تحت عنوان Hotfix ایجاد کرده و بعد از رفع مشکل بلافاصله مجددا روی Master ادغام شود؛ بدیهی است که بعد از ادغام روی Master، تغییرات منتشر میشود.مشخصات اصلی خوشه های زیر مجموعه از Hotfix به شرح زیر است: شامل نسخه هایی برای آپدیتهای بحرانی پروژه است.تغییرات آن از Master شروع میشود.درجه پایداری و اهمیت آن همانند Master است.همه اعضا فقط مجاز به اعمال Commit روی Hotfix هستند.تغییرات Hotfix تنها روی Develop و Master ادغام میشود.بعد از عمل Merge، نسخه به دست آمده مستقیماْ منتشر میشود.مشخصات پوشه Feature[s]، تالار آفرینشبه طور کلی، هر ویژگی جدیدی که قرار است به پروژه اضافه شود یا هر تغییری که قرار است در ویژگی های فعلی پروژه اعمال شود، تحت عنوان Feature-Branch روی پروژه اعمال میشود.همانطور که در جمله بالا مشخص است، برای تغییر روی ویژگی های فعلی پروژه باید از Feature استفاده کرد، حال اگر برای ویژگی موردنظر و Task مربوط به آن، از قبل یک Feature-Branch موجود باشد با ادغام Develop روی آن Branch، تغییرات را ادامه میدهیم در غیر این صورت برای تغییرات موردنظر یک ‌Feature-Branch جدید میسازیم.مشخصات اصلی خوشه های زیر مجموعه از Feature به شرح زیر است: شامل نسخه هایی با ویژگی های جدید است.تغییرات آن از Develop شروع می‌شود.همه اعضای پروژه فقط مجاز به اعمال Commit هستند.تغییرات آن تنها روی Develop ادغام می‌شود.درجه پایداری آن همانند Develop است.در زمان آینده و بعد از Merge میتواند پایدارتر شود.از روی Feature هیچ Sub-Branch دیگری ساخته نمی‌شود.در دسته‌بندی Branch های Git Flow، دو پوشه اختیاری به نام های ‌Bugfix و Upgrade مشاهده میشود که مشخصات و قوانین آن دقیقا مشابه Feature است؛ فقط در مورد Bugfix تنها نکته حائز اهمیت این است که اگر رفع مشکل مربوطه، فقط با یک Commit قابل حل باشد، تغییرات مربوط به آن روی Develop اعمال میشود در غیر این صورت یک Branch جدید تحت عنوان Bugfix برای آن ساخته میشود.مشخصات پوشه Release، محلی برای رستگاریهنگامی که تصمیم میگیریم یک نسخه جدید از آخرین تغییرات توسعه یافته پروژه را منتشر کنیم باید یکسری تغییرات روی پروژه اعمال کنیم که پیش زمینه انتشار نسخه جدید است، به عنوان مثال تغییر در Version Code پروژه، Rebuild پلاگین های مورد استفاده، جابجایی بین پلاگین های Market-Base مانند پلاگین های پرداخت و سایر تغییراتی که صرفا در فرایند آماده سازی خروجی برای نسخه جدید روی پروژه اعمال میشود؛ همگی از جمله تغییراتی هستند که تحت عنوان Release-Branch روی پروژه اعمال میشود.مشخصات اصلی خوشه های زیر مجموعه از Release به شرح زیر است: شامل نسخه های رسمی و منتشر شده از پروژه است.تغییرات آن از Develop شروع میشود.همه اعضای پروژه فقط مجاز به اعمال Commit هستند.تغییرات آن تنها روی Develop و Master ادغام میشود.درجه پایداری آن همانند Master استدامنه تغییرات آن فقط در حد تغییر روی Build tagها و پیشنیاز های New-Build است.چطور از Git Flow استفاده کنیم؟(۱) استفاده از Command و ابزار Git Flowهمانطور که قبلا اشاره کردم، Git Flow یکی از ابزارها و دستورات Git است که برای تسهیل فرایند و قوانین گفته شده توسعه یافته است و میتوانید با اجرای دستور &#x60;git flow&#x60; و دستورات start و finish نسبت به مدیریت Flow خود اقدام کنید. (۲) رعایت اصول مربوط به Git Flow در مدیریت Gitدر این روش با شناخت قوانین اولیه Git Flow و رعایت اصولی که در این مقاله مطرح میشود، میتوانید یک Flow سفارشی و هماهنگ با سیاست ها و اولویت های تیم توسعه خود، طراحی و پیاده سازی کنید؛ من با توجه به تجربه‌ای که در پیاده سازی این مسئله در یک پروژه نسبتا پیچیده داشتم پیشنهاد میکنم از روش دوم استفاده کنید که انعطاف پذیری بالاتری دارد و به مدیران پروژه آزادی عمل بیشتری میدهد.اصول اولیه در Git Flow برای مدیریت Gitدسته بندی توسعه دهندگان پروژهاولین اصل مهم در مدیریت Git، مشخص کردن نقش هر عضو از پروژه در سیستم Git است؛ شخصیت ها در پروژه به ۵ دسته زیر دسته‌بندی میشوند:Project ManagerMaintainerDeveloperReporterGuestبررسی تفاوت های هر کدام از نقش های بالا خارج از حوصله این مقاله است، در صورتی که علاقه‌مند به آشنایی با این مفاهیم هستید میتوانید مقالات زیر را مطالعه کنید:Repository roles for an organization on Git-Hub Permissions and roles on Git-Labپس از مشخص کردن نقش هر شخص در پروژه، عملیات های اصلی روی Branchها در Git بر اساس جدول زیر میتواند انجام شود:به دلیل عدم اثر گذاری نقش های Reporter و Guest در فرایند Git Flow، از آوردن آنها در جدول بالا خودداری کردیم.قوانین کنترل Branchهادومین و مهمترین اصل در مدیریت Git، قوانین مرتبط با کنترل Branchهای پروژه است چون ساختار و دسته‌بندی اصلی پروژه را مشخص میکند؛ این قوانین به شرح زیر است:در اینجا فرض بر این است که برای مدیریت پیشرفت پروژه از Jira استفاده میشود و ارتباط بین Jira با Git به درستی برقرار بوده و همچنین قابلیت Smart Comment نیز فعال است.اعمال مستقیم Merge رو هر Branch غیرمجاز است.هیچ کس حتی مدیر پروژه نمیتواند با دستور Merge این کار را مستقیما روی Branchها انجام دهد.تمام Mergeها در قالب یک Merge Request یا Pull Request برای مدیر پروژه ارسال میشود.پس از ارسال درخواست ادغام Branchها، افرادی که قبلتر به آن اشاره کردم با بررسی سلسله تغییرات و تایید آنها، عملیات Merge را انجام میدهند.نام‌گذاری هر Branch باید بر اساس قالب زیر انجام شود.{Base Git Address}/[Optional Branch Name]_[Jira Code]
origin/features/featureTestA_PFT-16شماره Task یا Jira Code در پیام هر Commit با قالب [ProjectCode-XX] درج شود.شماره Sprint یا Task در پیام هر Merge درج شود.مشخصا هر ادغامی که صورت میگیرد نتیجه تکمیل شدن یک Sprint یا Task است که با درج کردن شماره آن در پیام هر Merge میتوان در آینده، ارتباط بهتری با تغییرات انجام شده برقرار کرد و بررسی مشکلات احتمالی را ساده‌تر میکند.برای پروژه های مشترک نام گذاری Branchها یکسان است.به عنوان مثال، ممکن است یک پروژه شامل دو پروژه Server و Client باشد و یک Task نیازمند تغییرات مشترکی برای هر دو طرف پروژه باشد در چنین شرایطی نام‌گذاری Branch مربوطه برای هر دو طرف یکسان انجام میشود.بررسی یک نجات یافته توسط Git Flowدر انتهای مقاله نیز به بررسی یکی از پروژه هایی که بیش از ۱ سال، اصول و قوانین Git Flow روی آن رعایت شده میپردازیم:پ.ن ۱: به علت حفظ اصل محرمانگی داده و جلوگیری از آشکار شدن مشخصات پروژه، قسمت هایی که شامل اطلاعات حساس هستند از داخل تصویر بالا حذف شده‌اند. پ.ن ۲: قسمت های قرمز رنگ همان پیشوند مربوط به پروژه در Jira است که در نام‌گذاری Branchها رعایت شده است.حالا میتوانید سوالاتی که در ابتدای مقاله مطرح شد را اینجا از خود بپرسید و با توجه نکات مطرح شده و اطلاعات حاضر در تصویر بالا، به آن پاسخ دهید.منتظر پیشنهادات و انتقادات سازنده. شما دوستان در این حوزه هستم.موفق و سربلند باشید</description>
                <category>مهدی ایران نژاد</category>
                <author>مهدی ایران نژاد</author>
                <pubDate>Fri, 10 Mar 2023 02:40:19 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با App Cloning و جلوگیری از آن در اپلیکیشن های اندرویدی</title>
                <link>https://virgool.io/@mahdiirannezhad/preventing-android-app-cloning-jdhglm1lwtn2</link>
                <description>مقدمهتقلب و سواستفاده در هر زمینه ای یک امر اجتناب ناپذیره، ساختار و روانشناسی انسان به گونه ایه که تمایل به راحت طلبی داره، این راحت طلبی گاهی اوقات منجر به سواستفاده هایی میشه که یکی از اون موارد به تقلب اشاره میکنه.یکی از مباحثی که در آن چالش های بسیاری در زمینه بستن راه های تقلب بوجود آورده، بحث توسعه نرم افزار و اپلیکیشن هاست؛ در واقع واژه &quot;تقلب&quot; یا &quot;دور زدن&quot; در این مبحث یک اصطلاح کلی هستش و به خیلی از موارد  و مباحث اشاره میکنه؛ یکی از مواردی که میتونه به کسب و کار و توسعه دهندگان نرم افزار ها خسارت بزنه، بحث Clone کردن نرم افزار ها هست که من قصد دارم در این مقاله به معرفی این راه تقلب و بررسی اون بپردازم و با یک راه حل قطعی نسبت به جلوگیری مشکلات به وجود آمده، اقدام کنم.در این مقاله با یک روش قطعی جهت جلوگیری از تقلب در برنامه های اندرویدی آشنا شده و میتوانید از آن در داخل Android Studio برای اپلیکیشن ها یا Unity برای بازی های خود استفاده کنید.معرفی مشکلات و شناخت فلسفه Clone کردن در بحث تقلبیکی از راهکار هایی که توسعه دهندگان و مالکان یک نرم افزار جهت پیشرفت و افزایش تعداد کاربران خود، انجام میدهند؛ استفاده از روشی مرسوم به نام Invitation-User هست، در این روش کاربران در ازای معرفی کاربران جدید، از طرف نرم افزار خدمتی را به صورت جایزه یا امتیاز دریافت میکنند؛ نمونه های زیادی از این روش رو میتونم مثال بزنم، عمده بازی های موبایلی از این روش جهت بالا بردن تعداد کاربران خودشون استفاده میکنند یا برخی از نرم افزار های مربوط به ارزهای دیجیتال و سایر موارد مشابه نیز از این طریق به توسعه و پیشرفت خودشون مشغول هستند.بیایید یک بار فرآیند روش Invitation-User رو باهم بررسی کنیم:کاربر a نرم افزاری رو نصب میکنه (یا از قبل نصب کرده) و متوجه میشه بعد از معرفی کاربران جدید، از طرف برنامه یک روز اشتراک vip جایزه میگیرهکاربر a نرم افزار رو به کاربر b معرفی میکنه و ازش میخواد بعد از نصب، کدش رو به عنوان معرف وارد کنهکاربر b نرم افزار رو روی گوشی خودش نصب میکنه و کد معرفی که از کاربر a تحویل گرفته رو وارد برنامه میکنهکاربر a جایزه خودش رو دریافت میکنهحالا ممکنه کاربر a و کاربر b هر کدام نیز کاربران بیشتری رو معرفی و جوایز خودشون رو دریافت کنندچیزی که از این فرایند متوجه میشیم اینه که برای دریافت کد معرف، اون نرم افزار باید حتما روی دستگاه های جدید نصب بشه و به ازای هر نصب جدید ما صاحب یک کد معرف جدید هستیم؛ قطعا اگر در چنین فرایندی تقلب بشه، مالکان اون نرم افزار به سادگی هر چه تمام تر دچار ضرر و زیان جدی خواهند شد.حالا از چه طریق میشه در این فرایند تقلب کرد؟ اگه مراحل 2 و 3 توسط یک کاربر انجام بشه اینجا ما شاهد تقلبی هستیم که به دفعات بینهایتی میتونه تکرار بشه در واقع روشی که با استفاده از آن کاربر میتونه اقدام به تقلب کنه روشی به اسم App-Cloning هستش که با استفاده از برنامه هایی همچون Multi-Parallel, Dual-Space و سایر موارد مشابه، کاربر میتونه نسخه متعددی رو بدون شناخته شدن روی دستگاه خودش نصب کنه.روش کار این گونه برنامه ها به این شکله که در محیطی از سیستم عامل اندروید، محلی رو به صورت شبیه سازی شده در نظر میگیرند و فایل های اجرایی اپلیکیشن رو درون این محیط اجرا میکنند که باعث اجرای یک نسخه همسان با نسخه اصلی برنامه میشوند تا دقیقا مانند نسخه اصلی خودش رفتار کنه.شاید از خودتون بپرسید چرا سیستم عامل اندروید چنین اجازه ای رو میده که نسخه های متفاوتی از یک برنامه قابلیت اجرایی داشته باشند؟ چه دلیلی وجود داره که گوگل با آن همه سخت گیری های خودش در زمینه حق کپی رایت و حفظ حریم خصوصی و سایر قوانین مرتبط، باز هم اجازه چنین کاری رو میده؟جوابش خیلی ساده اس؛ چون این قابلیت یک قابلیت محشر، عالی و کاربردیه!عه چی شد؟ این همه در مورد فلسفه تقلب و ضرر و زیان هاش گفتی این همه حرف زدی، دلیل و منطق آوردی که این کار باعث خسارت های هنگفتی میشه، حالا میگی این قابلیت محشر و کاربردیه؟بله بازم میگم این قابلیت خیلی عالیه و کمک زیادی میکنه، بزارید یه مثالی بزنم، چاقو یکی از الزامات اصلی و از واجبات آشپزخانه است، ولی همین چاقو میتونه باعث قتل یک انسان بشه، نمیشه؟ هر چیز خوبی پتانسیل سواستفاده شدن داره و برعکس؛ بزارید مزیت و چرایی خوب بودن این قابلیت رو براتون با یک مثال ساده توضیح بدم شاید خودتون هم مجذوبش شدید. فرض کنید شما توسعه دهنده یک اپلیکیشن پیام رسان هستید، این پیام رسان الان منتشر شده و در آن با دوستان و آشنایان خود در ارتباط هستید، اکنون قصد دارید در تیم برنامه نویسی خود نسبت به توسعه و بهبود قسمتی از این پیام رسان یا اضافه کردن قابلیتی جدید به آن اقدام کنید؛ قطعا نسخه در حال توسعه و نسخه پایدار و در دسترس کاربران از یک سرور و دیتابیس یکسانی استفاده نمیکنند و شما برای تست و توسعه تغییرات خودتون نیاز دارید که نسخه اصلی رو از گوشی خودتون حذف کنید و نسخه تستی خودتون رو جایگزین کنید؛ آیا این کار آزار دهنده نیست؟ اگه با یکی از دوستان خودتون کار داشته باشید چه؟ قطعا باید بین نسخه های اصلی و تستی جابجا بشید و هزینه آن نیز صرف زمان جهت حذف و نصب نسخه های برنامه است.راه حل نسبتا خوب اینه که برای نسخه های تستی خودتون یک پکیج نیم مجزا در نظر بگیرید و موقع انشار، پکیج نیم اصلی رو جایگزین کنید؛ ولی آیا این روش مطمئن و جوابگوعه؟ پس تکلیف سرویس هایی که وابسته به پکیج نیم هستند چی میشه؟ سرویس هایی مثل FCM, Analytic Services و سرویس های پرداخت مثل پرداخت گوگل پلی و کافه بازار قطعا در این روش با مشکلاتی مواجه میشند و باعث مخلل کردن عملکرد برنامه میشوند.راه حل و درمان قطعی این مشکل فقط با قابلیتی به اسم Clone Install حل میشه؛ در واقع گوگل و سیستم عامل اندروید با این قابلیت به ما اجازه میده در محیطی از سیستم عامل، فایل های اجرایی یک نرم افزار رو بارگذاری کنیم و یک نسخه کلون از نسخه اصلی همون برنامه رو اجرا کنیم که عملکرد و شباهت 99 درصدی با نسخه اصلی داشته باشه؛ البته جهت دسترسی به این قابلیت و استفاده از آن باید حتما دسترسی Root به سیستم عامل اندروید داشته باشیم؛ همین امر باعث شده کمتر کسی به سراغش بره و روش شناخته شده ای نباشه...باز کردن دسترسی Root باعث باز شدن حفره ها و نقاط امنیتی سیستم عامل شده و از نظر کارشناسان امنیتی چنین کاری زیاد معقول نیست و به افراد عادی یا غیر حرفه ای پیشنهاد نمیشود.حالا برنامه هایی که بالاتر اشاره کردم، کاری مشابه با روش Clone Install رو انجام میدن، اون برنامه ها دقیقا فایل های اجرایی برنامه نصب شده داخل گوشی رو کپی و اجرا میکنند ولی ما در روش بالا از فایل های اجرایی خودمون استفاده میکنیم تا برنامه ای رو مجزا از نسخه اصلی خودش اجرا کنیم...راه حل جلوگیری از تقلب در روش App Cloningجهت جلوگیری از تقلب در این روش راه حل های مختلف و زیادی وجود داره، روش هایی مثل استفاده از شناسه های نرم افزاری مثل Android ID, Unique ID, FCM Caller ID, Firebase Client ID، کد هایی مثل Mac Address, IMEI, Sim ID و سایر موارد مشابه یا استفاده از روش های ترکیبی و خلاقانه از کد هایی که اشاره کردم تا حدی میتونه جلوی این روش از تقلب رو بگیره ولی به هیچ وجه راه حل و درمان قطعی نیست و همه این شناسه ها توسط App Clonerها قابل شبیه سازی و دور زدن هستش، در واقع این قابلیته که به خاطر همان دلایل بالا توسط سیستم عامل در اختیار App Cloner قرار گرفته میشه...جهت حل این مشکل باید در ابتدا با طرز کار App Cloner ها آشنا بشیم؛ همانطور که قبلا اشاره کردیم نجوه Clone کردن برنامه ها به این شکله که فایل های اجرای اون برنامه رو در یک محل تعریف شده با یک قالب و استاندارد مشخصی که از طرف سیستم عامل اندروید قابل شناسایی باشه، کپی کنیم؛ این کار باعث شناخته شدن یک نسخه جدید از همان نرم افزار خواهد بود.به طور پیش فرض محل ذخیره سازی اطلاعات و فایل های اجرایی برنامه ها در چنین آدرسی ذخیره میشوند:/data/user/0/com.example.my.application/filesممکن است ساختار این آدرس روی برند های مختلف گوشی های اندروید و نسخه های مختلف رام های اندرویدی متفاوت باشد ولی ساختار کلی همه آنها یکسان بوده شباهت زیادی با یکدیگر دارند.در واقع در سطح دسترسی Root، ساختار و سلسله مراتب ذخیره سازی فایل های یک برنامه در چنین قالبی قرار دارد و سیستم عامل از این ساختار به عنوان ساختار پیشفرض و اصلی خود جهت مدیریت فایل های برنامه ها استفاده میکند.ولی App Clonerها فقط مجاز به استفاده از این دو ساختار و سلسله مراتب جهت ذخیره سازی و Clone کردن برنامه ها هستند:/data/data/com.example.my.application/virtual/data/user/0/com.example.my.application/files
/data/user/999/com.example.my.application/filesدر حالت عادی تمام فایل ها و مسیر های پیش فرض ذخیره سازی اطلاعات و دسترسی های عادی کاربر در سیستم عامل اندروید از طریق آدرس /data/user/0/ امکان پذیر است این به این معناست که هر چیزی که ما از طریق برنامه مدیریت فایل اندروید به آن دسترسی داریم روی آدرس فوق ذخیره و بازیابی میشود و دسترسی مستقیم به هر جایی غیر از این سلسه مراتب نیازمند دسترسی Root است.اکنون پس از بررسی آدرس های بالا به این نتیجه میرسیم که آدرس ذخیره شدن یک اپلیکیشن Clone شده، دارای یکی از این دو خصوصیت زیر است:فایل های برنامه، زیرمجموعه پوشه های متعددی با نام پکیج نیم یا همان شناسه برنامه هستند، یعنی پکیج نیم در آدرس دهی فایل ها، روی بیش از 1 پوشه نام گذاری شده است.فایل های برنامه، زیرمجموعه پوشه 999 قرار گرفته است.پس ما با بررسی آدرس ذخیره سازی برنامه و مقایسه آن با دو شرط بالا میتوانیم نسخه Clone اپلیکیشن رو از نسخه Original تشخیص دهیم.پیاده سازی فنی و شناسایی هویت برنامه در زمان اجراروش کلی کار به این صورت هست که ابتدا با بررسی آدرس برنامه مطمئن میشیم که پوشه 999 زیرمجموعه آدرس دهی برنامه قرار نگرفته باشد؛ درصورتی جواب مثبت باشد هویت برنامه مشخص شده ولی اگر پوشه 999 یافت نشد، تعداد نقطه های پکیج نیم برنامه رو شمارش کرده و آن را با تعداد نقطه های موجود در آدرس برنامه مقایسه میکنیم، بعد از مقایسه این دو مقدار ما به جواب نهایی خود میرسیم و هویت برنامه ما مشخص میشود.استفاده از مکانیزم Anti-Clone برای Android Studio و اپلیکیشن های اندرویدیابتدا در محیطی از پروژه Android Studio خود یک کلاس از نوع Java به نام AntiCloner بسازید و دستورات زیر رو برای آن بنویسید:package com.test.antiappcloner;

import android.content.Context;

public class AntiCloner
{
    private static final AntiCloner ourInstance = new AntiCloner();
    private static final String DUAL_APP_ID_999 = &amp;quot999&amp;quot
    private static final char DOT = &#039;.&#039;;

    public static AntiCloner getInstance()
    {
        return ourInstance;
    }

    public boolean isAppCloning(Context context, String packageName)
    {
        int appPackageDotCount = getDotCount(packageName);
        String appStoragePath = context.getFilesDir().getPath();

        return appStoragePath.contains(DUAL_APP_ID_999) || getDotCount(appStoragePath) &gt; appPackageDotCount;
    }

    private int getDotCount(String path)
    {
        int count = 0;
        for (int i = 0; i &lt; path.length(); i++)
        {
            if (path.charAt(i) == DOT)
                count++;
        }

        return count;
    }
}حالا میتوانید در قسمتی از فرایند Loading برنامه خود، با دستور زیر از هویت اجرایی برنامه مطلع شوید و اقدام مناسبی را انجام دهید: AntiCloner.getInstance().isAppCloning(context, &amp;quotcom.example.my.application&amp;quot);استفاده از مکانیزم Anti-Clone برای Unity و بازی های اندرویدیجهت استفاده از این مکانیزم در یونیتی ما نیاز به ساختن پلاگین مخصوصی داریم تا با استفاده از دستورات کتابخانه JavaAndroid به اجرای کد های قسمت قبل و بررسی هویت بازی خودمون بپردازیم.کتابخانه JavaAndroid یک رابط بین کد زبان C#(سی چارپ) و زبان Java هستش که به ما اجازه میده در محیط بازی با پلاگین های اندرویدی مثل پلاگین های پرداخت یا امکانات سیستم عامل مثل GPS یا میکروفون، ارتباط بگیریم و اون ها رو کنترل کنیم؛ این کتابخانه ها فقط در یونیتی قابل دسترسی هست و یونیتی این کتابخانه ها رو صرفا برای توسعه بازی های اندرویدی در اختیار توسعه دهندگان خودش قرار داده.به همین خاطر ما باید ابتدا از کد هایی که بالاتر برای برنامه های اندرویدی نوشتیم یک پلاگین اندرویدی با فرمت AAR یا  JAR تولید کنیم و از آن خروجی بگیریم، در صورتی که با نحوه تولید و ساختن این پلاگین ها آشنایی ندارید میتوانید به مراجع آموزشی زیر مراجعه کنید:Creating an Android plugin for Unity3D - YouTubeCreating Android Plugin (AAR) for Unity with Android Toast - YouTubeCreating An Android Java Plugin For Unity3DCreating a native Android plugin for Unity3Dبا کمک دانش قبلی خود یا مراجع آموزشی بالا، کلاسی را که در قسمت قبل ایجاد کردیم رو برای پلاگین خودمون تعریف میکنیم و از آن خروجی aar یا jar میگیریم؛ سپس فایل پلاگین رو در مسیر Assets\Plugins\Android داخل پروژه یونیتی خود کپی میکنیم.فراموش نکنید در صورتی که در پروژه یونیتی خود از ProGuard استفاده میکنید حتما پکیج نیم پلاگین رو به دستورات ProGuard خود اضافه کنید؛ به عنوان مثال چیزی شبیه به این دستور را باید به فایل ProGuard  اضافه کنید تا بعدا در حین استفاده از پلاگین دچار مشکل نشوید:-keep class com.test.antiappcloner.** {*;}اکنون باید در محیط پروژه یونیتی خود یک کلاس جهت کنترل پلاگین بالا بسازیم؛ به عنوان مثال یک کلاس با نام AntiClonerPlugin میسازیم و داخل آن دستورات زیر را تعریف میکنیم:using System.Collections;
using UnityEngine;

public static class AntiClonerPlugin
{
    private const string pluginName = &amp;quotcom.test.antiappcloner.AntiCloner&amp;quot

    private static AndroidJavaClass _pluginClass;
    private static AndroidJavaObject _pluginInstance;
    private static AndroidJavaObject _context = null;

    private static AndroidJavaClass PluginClass
    {
        get
        {
            if (_pluginClass == null)
                _pluginClass = new AndroidJavaClass(pluginName);

            return _pluginClass;
        }
    }

    private static AndroidJavaObject PluginInstance
    {
        get
        {
            if (_pluginInstance == null)
                _pluginInstance = PluginClass.CallStatic&lt;AndroidJavaObject&gt;(&amp;quotgetInstance&amp;quot);

            return _pluginInstance;
        }
    }

    private static AndroidJavaObject Context
    {
        get
        {
            if (_context == null)
                _context = new AndroidJavaClass(&amp;quotcom.unity3d.player.UnityPlayer&amp;quot).GetStatic&lt;AndroidJavaObject&gt;(&amp;quotcurrentActivity&amp;quot);

            return _context;
        }
    }

    public static bool IsCloneApp()
    {
#if UNITY_EDITOR
        return false;
#endif

        return PluginInstance.Call&lt;bool&gt;(&amp;quotisAppCloning&amp;quot, Context, &amp;quotcom.example.my.unitygame&amp;quot);
    }
}حالا میتوانید در قسمتی از فرایند Loading بازی خود، با دستور زیر از هویت اجرایی بازی مطلع شوید و اقدام مناسبی را انجام دهید یا از آن برای شناسایی کاربران متخلف استفاده کنید:AntiClonerPlugin.IsCloneApp();فراموش نکنید که توابع کتابخانه JavaAndroid فقط روی سیستم عامل اندروید و  روی گوشی اجرا میشوند و در صورت اجرا روی یونیتی، خطا میدهند و اجرا نمیشوند، جهت تست عملکرد و نحوه اجرای پلاگین در داخل یونیتی میتوانید به صورت دستی مقدار بازگشتی تابع IsCloneApp را در بخش زیر تغییر دهید، این قسمت فقط در محیط ادیتور اجرا میشود و در زمان خروجی گرفتن، Compiler آن را در نظر نمیگیرد:#if UNITY_EDITOR
         return false; 
#endifمنتظر نظرات سازنده و بازخورد های شما عزیزان و متخصص های این حوزه هستمموفق و پیروز باشید</description>
                <category>مهدی ایران نژاد</category>
                <author>مهدی ایران نژاد</author>
                <pubDate>Tue, 04 Jan 2022 14:45:40 +0330</pubDate>
            </item>
                    <item>
                <title>انتشار فایل aab در کافه بازار</title>
                <link>https://virgool.io/@mahdiirannezhad/aab-publishing-in-cafebazaar-vfg2fkcjmrb7</link>
                <description>اگر کسی هستید که صرفا برای مطالعه نحوه انتشار فایل aab به این مقاله مراجعه کردید، پیشنهاد میکنم به بخش سوم این مقاله که کمی پایین‌تر نوشته شده، مراجعه کنید؛ بخش های ابتدایی بیشتر برای آشنایی با فرمت aab و چرایی استفاده ما از آن و همچنین دلیل نوشتن این مقاله هست.بخش اول: معرفی فرمت AAB، کاربرد و مزایای آناگه توسعه دهنده اپ یا بازی‌ساز پلتفرم اندروید باشید و بخواهید اپ یا بازی خودتون رو در محیط کافه بازار منتشر کنید، حتما با واژه من‌درآوردی و عجیب غریب رهانش از سوی بازار آشنا هستید؛ داخل هر رهانش ما میتونیم چندین فایل APK برای معماری های مختلف از پردازنده ها و سخت‌افزار ها منتشر کنیم؛ من تا جایی که اطلاع دارم گوشی های اندرویدی از لحاظ سخت‌افزاری همواره بر اساس 3 معماری زیر تولید و ساخته میشوند:ARM: ARMv7 or armeabi or v7aARM64: AArch64 or arm64 or v8ax86: x86 or x86abi (در حال منسوخ شدن)برای این که ما بتوانیم از بابت پشتیبانی برنامه خود پس از انتشار و صحت عملکرد آن روی تمامی دستگاه ها مطمئن باشیم، باید در هنگام خروجی گرفتن از کد های خود، تنظیمات مربوط به معماری های پردازنده رو بررسی کرده و حدالامکان هر 3 معماری بالا رو برای خروجی گرفتن فعال کنیم؛ تا اینجا مشکلی نیست و همه چیز بر وقف مراد است؛ ولی پس از خروجی گرفتن سر و کله یه مشکل خیلی جزئی و بسیار آزار دهنده پیدا میشه که کاملا عادی و منطقی هستش و اونم افزایش قابل توجه حجم فایل خروجی ما از برنامه و پروژه است.من یک توسعه دهنده بازی های اندرویدی هستم که با انجین یونیتی کار میکنم، پس تجربه خودم در برخورد با این مشکل رو اینگونه توضیح میدم که به ازای انتخاب هر کدوم از معماری های بالا چیزی در حدود 10 الی 15 مگابایت به حجم خروجی بازی من اضافه میشه، یعنی اگه حجم بازی من 30 مگابایت باشه و هر 3 معماری رو انتخاب کنم در خوش‌بینانه ترین حالت، حجم نهایی بازی من حداقل حدود 60 مگابایته و این خیلی بده، حداقل برای کاربری میخواد بازی رو دانلود کنه جلوه خوبی نداره.راه حل بسیار ساده برای رفع این مشکل اینه که از پروژه خودمون بر اساس هر معماری، خروجی APK مجزایی بگیریم و منتشر کنیم، خبر خوب اینه که کافه بازار این روش رو پشتیبانی میکنه و شما میتونید با ایجاد یک رهانش فایل های apk مربوط به هر معماری رو به صورت مجزا آپلود و منتشر کنید.روش بالا شاید جوابگو باشه ولی اصلا پیشنهاد نمیشه، دلیلش اینه که شما باید همواره Version Code مربوط به فایلی که داخل بازار آپلود میکنید از Version Code آخرین نسخه آپلود شده بیشتر باشه، یعنی اگر آخرین ورژن بازی یا برنامه منتشر شده شما عدد 3 باشه، طبق معمول باید فایلی که آپلود میکنید دارای ورژن 4 باشه ولی در این روش باید به ازای هر معماری که خروجی میگیرید یک ورژن مجزا در نظر بگیرید تا بتوانید فایلتون رو در پنل بازار آپلود کنید و اگر هر سه معماری رو انتخاب کنید باید به ترتیب ورژن 4، 5 و 6 رو برای هر نسخه از فایل apk در نظر بگیرید؛ این مشکل نه تنها از مشکل قبلی بدتره بلکه به بدترین نحو ممکن فاجعه‌اس؛ توضیح این که چرا این یک مشکل بدتره اصلا داخل این مقوله نمیگنجه ولی با کمی فکر به عمق فاجعه پی میبرید....دقیقا نمیدونم هدف بازار از گذاشتن این قانون چیه و با خودش چه فکری کرده چنین فرایندی رو برای خودش تعریف کرده؟ حداقل باید به کاربر اجازه بده در یک رهانش بر اساس apk ها و معماری هرکدوم از آنها ورژن کد یکسانی رو آپلود کنه...همه این حرفا رو زدیم که اینجا بگیم چی میشد اگه میتونستیم فایلی رو منتشر کنیم که تمام این معماری هارو درون خودش داشته باشه ولی کاربر ها بتونند در هنگام دانلود برنامه فقط معماری مخصوص خودشونو با حجم کمتر و بدون این مشکلاتی که مطرح کردیم، دریافت کنند؟اینجاست که فرمت AAB میاد وسط میدان، این فرمت که مخفف Android App Bundles هست یک راهکار مناسب برای رفع مشکلات انتشار اپلیکیشن هاست که توسط گوگل در سال 2018 معرفی شد؛ در این راهکار شما نیازی به ساختن فایل apk ندارید و تمامی کد های بومی اپلیکیشن خود، قسمت های اصلی پروژه و قسمت های مربوط به هر معماری رو به صورت محافظت شده در قالب یک فایل تحت فرمت aab قرار میدید و گوگل‌پلی هنگام انتشار اون میاد به صورت خودکار فایل apk مناسب برای هر کاربر و دستگاه رو میسازه و در اختیارش میده...فایل aab مزایای زیادی داره و مشکل افزایش حجم و تداخب معماری های مختلف فقط و فقط یکی از مشکلاتی هست در این فرمت حل شده‌اند و کلی مشکلات امنیتی دیگر هستند در این فرمت نیز برطرف شده‌اند که توضیح و تشریح آنها خارج از بحث این مقاله است.پیشنهاد میکنم برای آشنایی بیشتر با این فرمت این مقاله را مطالعه کنید: https://dgto.ir/286lولی هدف ما انتشار در گوگل پلی نیست بلکه ما قصد داریم فایل aab خودمون رو داخل کافه بازار منتشر کنیم؛ در ادامه نحوه انجام این فرایند رو براتون شرح میدم....بخش دوم: کافه بازار و فرمت AABمثل همیشه ما با یه سری فرایند های پیچیده بازار مواجه هستیم، یه سری کار که قطعا عبارت &quot;لقمه را دور سر گرداندن&quot; در مقابل این پیچیدگی هایی که بازار ایجاد کرده، سر تعظیم فرود می‌آورد؛ من قصد ندارم در این مقاله به UX و تجربه کاربری سیستم بازار در آپلود و انتشار برنامه ها بپردازم چون واقعا کاری بی‌فایده و حوصله سر بری هست.کاری که من کردم مطالعه داکیومنت مضخرف بازار در مورد انتشار باندلی یا aab، سرچ های مختلف در گروه های تلگرامی و سایت های اینترنتی بود که در نتیجه با ساختن یک فایل bath به نتجیه مطلوب رسیدم و بدون کوچک‌ترین درگیری با یکسری کامند گمراه کننده تونستم فایل باینری امضای برنامه رو بسازم.قبل از این که به سراغ اصل ماجرا برم اول توضیح میدم که این فایل چیه و چه کاربردی داره.کل این ماجرا بر میگرده به همون فایل aab که توسط گوگل ایجاد شده، گوگل این فرمت رو به قول معروف برای گوگل‌پلی اختراع کرد تا از یه سری مشکلات انتشار جلوگیری کنه و الحق به خوبی تونست از پسش بر بیاد و چند روز پیش هم خوندم که فرمت apk کلا منسوخ شده و گوگل پلی دیگه از این فرمت پشتیبانی نمیکنه ولی توسعه‌دهنده ها میتونن برای خودشون از این فرمت استفاده کنند.پس نتیجه میگیریم که فایل aab به طور پیش‌فرض برای گوگل‎‌پلی ساخته شده و جایی کاربرد نداره؛ چیزی که این وسط اتفاق میافته اینه که گوگل فایل aab مارو تحویل میگیره و ازش apk میسازه و در اختیار کاربر ها قرارش میده، برای این کار گوگل نیاز داره apk هایی که میسازه رو امضا کنه به همین خاطر قبل از هر کاری از ما فایل مربوط به کلید امضای برنامه رو تحویل میگیره و بر اساس اون یک کلید امضای خصوصی میسازه، حالا گوگل تمام apk هایی که میسازه رو به نمایندگی از ما امضا و منتشر میکنه.تا اینجا ما با ساز و کار گوگل‌پلی آشنا شدیم، حالا بازار هم میخواد همچنین کاری رو با فایل های aab ما بکنه ولی یه نکته ای اینجا هست، از اون جایی که ما خیلی به سیستم های ایرانی و امنیتشون اعتماد داریم، در اختیار گذاشتن فایل کلید امضای پروژه در چنین سیستمی فقط یک حماقت محضه و کافه بازار هم به خوبی بر این مسئله واقفه؛ پس این جا یک سیستم و ابزاری رو پیاده کرده تا ما با استفاده از اون یک کلید رمز شده از کلید اصلی خودمون بسازیم و اون رو در اختیارشون بزاریم تا با استفاده از اون کلید رمز شده فایل های apk که میسازه رو برامون امضا کنه.سیستم های بانکی، مخابراتی و اپراتور های تلفن همراه کشور رو هک کردن این که دیگه براشون مثه آب خوردنه :|بخش سوم: ساخت فایل باینری امضا برای انتشار باندلی کافه بازارپیش‌نیاز این کار نصب بودن JDK و JRE نسخه 8 یا بالاتر روی سیستم و اضافه کردن مسیر آن در متغیر path و JAVA_HOME ویندوز است؛ جهت اطلاعات بیشتر میتونید عبارت &quot;config java_home&quot; رو در گوگل سرچ کرده یا این مقاله را مطالعه کنید.کافه بازار برای ساخت فایل باینری امضا ابزار اپن سورسی به اسم Bundle Signer رو توسعه داده که سورس کد و مستنداتش رو میتونید از گیت‌هاب این پروژه دریافت کنید؛ کلا یه سری مستندات چرت پرتی نوشته که باید چندین بار مطاالعه بشه یا خیلی در این زمینه حرفه ای باشید تا بلکه متوجه معنا و مفهومش بشیددر قدم اول آخرین نسخه این ابزار رو از لینک بالا دانلود کرده و در محلی مناسب ذخیره کنید.اکنون باید کد های زیر رو در notepad بنویسیم:@echo off
cls
set /p aabFilePath=&amp;quotEnter the *.aab File Name or Fully Address Path: &amp;quot
echo.
set /p saveLocation=&amp;quotEnter Loaction for Storing the Generated *.bin File &#40;[dot] . for Current Location&#41;:&amp;quot
echo.
set /p ksPath=&amp;quotEnter the *.keystore File Path: &amp;quot
echo.
set /p ksAlias=&amp;quotEnter the Key Store Alias: &amp;quot
echo.
set /p ksPass=&amp;quotEnter the Key Store Password: &amp;quot
echo.
set /p aliasPass=&amp;quotEnter the Alias Password: &amp;quot
echo.
java -jar bundlesigner-0.1.7.jar genbin -v --bundle %aabFilePath% --bin %saveLocation% --v2-signing-enabled true --v3-signing-enabled true --ks %ksPath% --ks-key-alias %ksAlias% --ks-pass pass:%aliasPass% --key-pass pass:%ksPass% --pass-encoding utf-8
echo.
pauseکد های بالا رو با یک اسم دلخواه و با پسوند bat یا cmd در کنار فایل bundle-signer ذخیره کنید.نسخه bundle-signer که من استفاده کردم نسخه 0.1.7 بود و همانطور که در کد های بالا مشاهده میکنید عبارت &quot;bundlesigner-0.1.7.jar&quot; در واقع به اسم و نام همان فایل اشاره میکند، پس در صورتی که شما ورژن و نام متفاوتی با این نسخه را استفاده میکنید حتما نام مربوط به این نسخه را درون کد بالا اصلاح کنید.پیشنهاد من استفاده از همان نسخه 0.1.7 برای انجام این فرایند است چون از بابت عملکرد آن اطمینان دارم...از این پس هر زمان که نیاز به ساخت فایل باینری امضا داشتید، روی فایلی که در مرحله قبل ساختید راست کلیک کنید و روی گزینه run as administrator کلیک کنید و به ترتیب مقادیر مورد نیاز خود را بر اساس توضیحات زیر، وارد پنجره باز شده کنید...در صورتی که در داخل اطلاعات خود از کارکتر space یا فاصله استفاده شده باشد باید آن را درون دابل کوتیشن یا &quot;&quot; قرار دهید...اطلاعات درخواستی از طرف برنامه به ترتیب زیر است:آدرس فایل aabمحل ذخیره سازی فایل باینری امضا؛ درصورتی که از . یا نقطه استفاده کنید، فایل ساخته شده در کنار فایل bundle-signer ذخیره میشود.آدرس فایل امضا با پسوند keystoreنام مستعار یا Alias استفاده شده در امضای پروژهپسورد مشترک نام مستعار و کلید امضابه عنوان مثال چیزی که در آخر داخل برنامه نمایش داده میشود شبیه به مقادیر زیر است:Enter the *.aab File Name or Fully Address Path: &quot;C:\Projects\My App Build.aab&quot;Enter Loaction for Storing the Generated *.bin File &#40;[dot] . for Current Location&#41;: .Enter the *.keystore File Path: &quot;C:\Projects\My Key File.keystore&quot;Enter the Key Store Alias: KEY_ALIAS_NAMEEnter the Key Store Password: KEY_PASSWORDنمونه فرایند انجام شده در برنامهپس از تائید اطلاعات وارد شده، مدت زمانی را بر اساس نوع پروژه و میزان حجم برنامه سپری میشود و در نهایت پیغامی چاپ میشود که نمایانگر موفقیت و اتمام فرایند فوق است.اکنون کار ما تمام شده و شما میتوانید پس از آپلود فایل aab در پنل رهانش کافه بازار، فایل باینری امضا را نیز در اختیار کافه بازار قرار دهید.نکته مهم در خصوص کد ها و فرایند بالاکد های بالا برای کلید های امضای از نوع Java Key Store یا keystore.* نوشته شده‌اند؛ در صورتی که از کلید های نوع PKCS#8 استفاده میکنید، در بخش نظرات اعلام کنید تا کد مربوط به این نوع از کلید ها را هم به مقاله اضافه کنم. «چون تجربه کار با این نوع کلید امضا رو نداشتم، نوشتن کد و امتحان کردن آن نیاز به کمی تحقیق و بررسی داره»منتظر نظرات و بازخورد های شما عزیزان هستمموفق و پیروز باشید</description>
                <category>مهدی ایران نژاد</category>
                <author>مهدی ایران نژاد</author>
                <pubDate>Sat, 02 Oct 2021 02:14:13 +0330</pubDate>
            </item>
                    <item>
                <title>راهکاری جالب برای توسعه دهندگان اندروید!</title>
                <link>https://virgool.io/@mahdiirannezhad/remote-install-c3cyuilqnfrk</link>
                <description>نکاتی که قبل از مطالعه این مقاله باید بدانید:- این راهکار فقط برای توسعه‌دهنگانی که از ویندوز استفاده میکنند کارایی داشته و به دلیل استفاده از کد ها و تنظیمات مربوط به ویندوز، امکان استفاده از آن درون سیستم عامل های مکینتاش یا لینوکس وجود ندارد.- حتما از فعال بودن قابلیت USB Debugging روی گوشی اندرویدی خود مطمئن باشید.یک دغدغه مهماکثر ما برنامه نویس ها وقتی که داریم روی یک پروژه اندرویدی کار میکنیم، فرقی نداره پروژمون یک بازی جذاب باشه یا یک اپلیکیشن کاربردی، یکی از دغدغه های مهم و درگیری های ذهنیمون اینه که برنامه‌امون رو روی موبایل تست و دیباگ کنیم؛ در واقع این امر یکی از کار های مهم، تکراری و البته زمانبر در فرآیند توسعه نرم‌افزار هستش.این فرآیند برای اکثر توسعه‌دهندگان اندروید شامل مراحل مختلف و زیادی است ولی مهمترین بخش های آن شامل مراحل زیر است:خروجی گرفتن از پروژهوصل کردن گوشی به سیستمانتقال فایل apk روی گوشیرفتن به محیط گوشی و نصب برنامهمراجعه به محیط Log-Cat برای رصد تست لاگ‌هااجرای برنامه و انجام خواسته های خودفکر این که ما هر دفعه بعد از هر بار خروجی گرفتن از پروژه بخواهیم این 6 مرحله رو طی کنیم کمی آزار دهنده است؛ حالا، اگر ما بتونیم مراحل 2 تا 5 این فرآیند تکراری رو برای هر بار تست و دیباگ برنامه های خود حذف کنیم چه؟من قصد دارم در این راهکار کاری کنم تا شما بعد از خروجی گرفتن معطل هیچ چیزی نشوید و خیلی سریع به سراغ اصل کار بروید و عملا مراحل 2 تا 5 رو حذف کنید.معرفی روش Remote Installمن اسم این راهکار رو Remote Install میزارم و روش کلی کارکرد آن نیز به این صورته که شما بعد از خروجی گرفتن از پروژه کافیه فایل apk رو در محیط ویندوز اجرا کنی، با این کار فایل apk به صورت خودکار روی گوشی نصب میشه و تنها کاری که باید بکنید اجرای برنامه برای تست هستش...نکته مهم: من به عنوان یک توسعه دهنده بازی که از انجین یونیتی استفاده میکنم سعی کردم در این مقاله راهکاری رو ارائه کنم که به دلیل سخت و تکراری بودن فرایند تست و دیباگ بازی‌ها و همچنین نبودن ابزار های مستقل برای این کار بیشتر مناسب توسعه‌دهندگان بازی هستش، ولی با این حال نکاتی رو خواهم گفت شاید مناسب سایر توسعه دهندگان اندروید باشد و خواندنش خالی از لطف نخواهد بود.اضافه کردن آدرس فایل adb.exe به Pathدر اولین مرحله باید اطمینان داشت که آدرس فایل adb.exe در متغیر Path مربوط به ویندوز ذخیره شده است؛ این فایل معمولا در پوشه platform-tools زیر مجموعه پوشه Android SDK قرار دارد و برای این که بتوانیم دستورات خود را اجرا کنیم نیاز به ثبت آدرس محل ذخیره سازی آن در Path ویندوز داریم.برای این منظور در محیط Start ویندوز، کلمه cmd رو سرچ میکنیم و سپس روی برنامه Command Prompt یا cmd.exe راست کلیک کرده و گزینه Run as Administrator را انتخاب میکنیم تا محیط خط فرمان برایمان باز شود. سپس دستور زیر را داخل آن مینویسیم:set PATH=%PATH%;ADB_PATHدر دستور بالا به جای عبارتADB_PATHباید آدرس محل قرارگیری فایل adb.exe را قرار دهیم؛ به عنوان مثال دستور زیر نوشته و آن را اجرا میکنیم.set PATH=%PATH%;D:\Android-SDK\platform-tools\پیاده سازی دستورات نصب فایل apkاکنون باید دستورات نصب فایل apk روی گوشی را بنویسیم، برای این کار notepad رو باز کرده و دستورات زیر رو داخل آن مینویسیم:@echo off
cls
title Installing &amp;quot%~NX1&amp;quot
echo Installing &amp;quot%~NX1&amp;quot on Device:
adb install &amp;quot%1&amp;quot
pause&gt;nulحالا باید دستورات بالا را در محلی ذخیره کنیم، به این منظور یک نام مناسب برایش در نظر میگیریم و آن را با پسوند cmd ذخیره میکنیم؛ به عنوان مثال آن را  Remote_Install.cmd ذخیره میکنیم.نکته مهم: در انتخاب محل ذخیره سازی فایل Remote_Install.cmd دقت کنید، چون به هیچ وجه نباید این فایل حذف یا جابه‌جا شود.تنظیمات مورد نیاز Registryاکنون باید در Registry تنظیماتی را انجام دهیم تا فایل های apk در محیط ویندوز قابلیت اجرایی داشته باشند یعنی بتوانیم با دابل کلیک یا فشردن کلید Enter روی آن، آنها را اجرا کرده و عملیات موردنظر خود را اجرا کنیم؛ برای این منظور notepad رو باز کرده و دستورات زیر رو داخل آن مینویسیم:Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.apk]
@=&amp;quotapkfile&amp;quot

[HKEY_CLASSES_ROOT\.apk\shell]

[HKEY_CLASSES_ROOT\.apk\shell\apkinstaller]
@=&amp;quotRemote Install on Device&amp;quot

[HKEY_CLASSES_ROOT\.apk\shell\apkinstaller\command]
@=&amp;quotCMD_FILE_ADDRESS %1&amp;quotحالا آدرس محل ذخیره سازی  فایل Remote_Install.cmd که در مرحله قبلی ایجاد کردیم رو جایگزین عبارتCMD_FILE_ADDRESSدر کد بالا میکنیم.در حین نوشتن آدرس باید به این نکته توجه کرد که آدرس دهی فایل‌ها و پوشه‌ها در Registry به صورت Double Back Slack انجام میشود؛ یعنی به جای استفاده از \ باید از \\ در مسیر آدرس دهی فایل‌ها و پوشه‌ها استفاده کرد؛ به عنوان مثال باید آدرس زیر:D:\Android-Development-Tools\Remote_Install.cmdبه این صورت نوشته شود:D:\\Android-Development-Tools\\Remote_Install.cmdبا رعایت نکته ذکر شده، باید خط آخر در کد های بالا را به این صورت بنویسیم:@=&amp;quotD:\\Android-Development-Tools\\Remote_Install.cmd %1&amp;quotحالا باید دستورات نوشته شده و تغییرات را با پسوند reg در محلی ذخیره کنیم؛ به عنوان مثال آنها را در فایلی به نام Registry_Config.reg ذخیره میکنیم.پس از ذخیره سازی، فایل Registry_Config.reg را اجرا کنید و گزینه yes را بزنید تا فرآیند اعمال تنظیمات کامل شود.با اجرای این دستورات، این امکان برای ما فراهم میشود که بتوان به فایل های apk به عنوان یک فایل اجرایی نگاه کرد و اگر روی فایل های apk راست کلیک کنیم میبینیم که عبارت Remote Install on Device به ابتدای منوی باز شده، اضافه شده است.نکته مهم: باز هم تذکر داده میشود که به هیچ وجه فایل Remote_Install.cmd رو حذف یا جابه‌جا نکنید چون با اجرای فایل apk در واقع سیستم عامل به دنبال فایل فوق رفته و آن را اجرا میکند.اکنون ما میتوانیم با وصل کردن گوشی به سیستم و اجرای یک فایل apk آن را روی گوشی خود نصب کنیم ولی صبر کنید، من میخوام کاری کنم که نیاز نباشه برای هر بار خروجی گرفتن و نصب کردن برنامه، گوشی رو به سیستم وصل کنیم، بلکه میخواهم از طریق شبکه بی‌سیم یا همون Wi-Fi این کار رو انجام بدیم...نکته مهم: برای درست اجرا شدن این ترفند باید فقط یک دستگاه اندرویدی به سیستم متصل باشه و در صورت وصل بودن بیش از یک دستگاه به صورت همزمان، خطایی مبتنی بر این موضوع برایتان نمایش داده میشود.شناسایی گوشی از طریق شبکه بی‌سیمبرای این که سیستم عامل بتونه گوشی رو از طریق شبکه بی‌سیم شناسایی کنه و روی آن کنترل داشته باشه باید اول از همه گوشی رو از طریق شبکه بهش معرفی کنیم؛ برای مثال IP آدرس 192.168.70.13 آدرس گوشی من تو شبکه هستش و وقتی من یک فایل apk رو اجرا میکنم قصد دارم اون فایل رو روی IP مورد نظرم نصب کنم.برای این منظور باید اول از همه یک IP آدرس ثابت یا استاتیک برای گوشیمون تو شبکه تعریف کنیم تا تغییر نکنه چون در صورت تغییر کردن IP گوشی، باید IP تنظیم شده در داخل تنظیماتی که در ادامه به آن اشاره میکنم نیز تغییر دهیم.نکته مهم: در صورتی که نمیدونید چجوری IP یک دستگاه رو داخل شبکه استاتیک کنید، پیشنهاد میکنم مطالب زیر رو دنبال کنید:تنظیم IP استاتیک در روتر یا مودمتنظیم IP استاتیک در گوشی اندرویدیپس از تعریف IP استاتیک برای گوشی خود باید آن را در سیستم ذخیره کنیم؛ برای این منظور دقیقا طبق روش قبل محیط خط فرمان یا همان Command Prompt را باز کرده و دستور زیر را داخل آن مینویسیم:setx MyStaticMobileIPAddress &amp;quotX.X.X.X&amp;quot /Mدر دستور بالا به جای عبارت X.X.X.X آدرس IP استاتیک خود را وارد میکنیم؛ به عنوان مثال دستور زیر را وارد و اجرا میکنیم:setx MyStaticMobileIPAddress &amp;quot192.168.70.13&amp;quot /Mنکته مهم: در صورت نیاز به تغییر IP آدرس گوشی خود، میتوانید دستور ذکر شده رو مجددا با IP جدید اجرا کنید.حالا باید دستورات لازم برای اتصال گوشی به سیستم رو بنویسیم؛ برای این منظور notepad رو باز کرده و دستورات زیر رو مینویسیم:@echo off
cls
echo 1. Connect to Device via WiFi
echo 2. Disconnect All Devices
echo 3. Show All Devices
echo 4. Connect to %MyStaticMobileIPAddress%
choice /C 1234 /n /m &amp;quotPress 1,2,3,4 for Action: &amp;quot 
cls
goto l%ERRORLEVEL%

:l1
echo Make Sure the Device is Conncted via USB Port and Enabled &amp;quotUSB Debugging&amp;quot on Mobile Settings
pause&gt;nul
adb disconnect
adb tcpip 5555
set /p ip=&amp;quotEnter Device IP Address from your Network: &amp;quot
adb connect %ip%
cls
echo You can Disconnect your Device from USB Port...

:l3
adb devices
pause&gt;nul
exit

:l2
adb disconnect
pause&gt;nul
exit

:l4
adb disconnect
adb tcpip 5555
adb connect  %MyStaticMobileIPAddress%
cls
echo You can Disconnect your Device from USB Port...
goto l3حالا دستورات بالا رو در فایلی با پسوند cmd در جایی که دسترسی به آن برامون راحت و سریعه ذخیره میکنیم؛ به عنوان مثال آن را با اسم Set Device on WiFi.cmd در دسکتاپ ویندوزمون ذخیره میکنیم.معرفی گوشی به ویندوز از طریق شبکهاکنون فایل Set Device on WiFi.cmd که در مرحله قبلی ساختیم رو اجرا میکنیم، همانطور که میبینید چهار گزینه به صورت زیر برایمان نمایش داده میشود:Connect to Device via WiFiDisconnect All DevicesShow All DevicesConnect to 192.168.70.13نکته مهم: در اینجا فرض بر این است که IP گوشی 192.168.70.13 است و این مقدار بستگی به IP که در مراحل قبلی ذخیره کردید، داره.شرح گزینه های بالا به صورت زیر است:این گزینه به صورت مستقیم از شما IP دستگاه مورد نظر رو دریافت کرده و سپس اقدام به اتصال به همان IP وارد شده میکند، این گزینه بیشتر مناسب زمانی است که قصد دارید گوشی هایی به غیر از گوشی خود را به سیستم خود معرفی کنید.این گزینه اتصال تمام گوشی هایی که تاکنون از طریق شبکه به سیستم معرفی شده‌اند رو قطع میکند.این گزینه وضعیت تمام دستگاه های متصل به سیستم رو نمایش میدهد.این گزینه نیز گوشی خود را به سیستم معرفی میکند.نکته مهم: در زمانی که قصد معرفی کردن گوشی از طریق شبکه به سیستم را دارید، گوشی باید از طریق سیم به سیستم متصل باشد؛ بعد از انجام فرآیند معرفی میتوانید سیم گوشی را از سیستم جدا کنید؛ همچنین این اتصال بین گوشی و سیستم از طریق شبکه تا زمانی که از شبکه خارج نشده‌اید، ادامه خواهد داشت.خب بلاخره کار کد نوشتن و انجام تنظیمات تمام شد؛ وقت آن رسیده که نتیجه کارمون رو ببینیم؛ برای این منظور یک بار گوشی رو با استفاده از سیم به سیستم خود وصل کرده و قفل آن را نیز باز میکنیم سپس فایلی که در آخرین مرحله ساختیم رو اجرا کرده و عدد 4 را وارد میکنیم؛ پس از اتمام فرآیند، میتوانید لیست دستگاه های متصل به سیستم رو مشاهده کنید که در حال حاضر 2 دستگاه را نمایش میدهد، هر دو آنها همان گوشی ما هستند فقط با این تفاوت که یکی از طریق شبکه و دیگری از طریق سیم به سیستم معرفی شده است.حالا میتوانیم گوشی خود را از سیستم جدا کرده و فایل های apk یا همان پروژه های خود را به صورت مستقیم از طریق شبکه روی گوشی نصب کنیم.اجرای مستقیم Log-Cat پس از نصب برنامه روی گوشیدر این مرحله نیز با اضافه کردن دستورات زیر به انتهای فایل Remote_Install.cmd میتوانیم بلافاصله بعد از نصب برنامه یا بازی خود، وارد محیط Log-Cat شویم؛ برای این منظور روی فایل فوق راست کلیک کرده و گزینه Edit را میزنیم سپس دستورات زیر را به انتهای فایل اضافه میکنیم:choice /C yn /n /m &amp;quotOpen The Logcat? (y/n)&amp;quot 
goto l%ERRORLEVEL%

:l1
title Device Logcat
cls
adb logcat -c
adb logcat -s Unity IabHelper

:l0
exitنکته مهم: در دستورات بالا نوشتن دو عبارت Unity و IabHelper باعث میشود که فقط لاگ های مربوط به یونیتی و سیستم پرداخت درون برنامه‌ای برایتان نمایش داده شود که بیشتر مناسب بازی‌سازان یونیتی است و در صورتی که بخواهید علاوه بر لاگ های گفته شده، لاگ های سیستمی اندروید را نیز مشاهده کنید میتوانید این دو عبارت رو از دستورات بالا حذف کنید.از این پس بعد از اجرا و نصب فایل apk سوالی مبتنی بر اجرای Log-Cat از ما پرسیده میشود که با فشردن دکمه y میتوانیم وارد محیط Log-Cat شویم...سخن پایانیدر این آموزش سعی کردم راهکاری رو در اختیار توسعه دهندگان قرار دهم که خودم به عنوان یک تجربه مفید و کاربردی ازش استفاده میکنم و بهم کمک زیادی در فرآیند تست و دیباگ بازی های یونیتی کرده.در ضمن از شما نیز بابت مطالعه این مقاله سپاسگذارم و منتظر پیشنهادات، نظرات و انتقادات سازنده شما در این زمینه هستم.همچنین میتوانید از طریق ایمیل yp2014ir@gmail.com با من در تماس باشید.</description>
                <category>مهدی ایران نژاد</category>
                <author>مهدی ایران نژاد</author>
                <pubDate>Thu, 19 Nov 2020 23:46:19 +0330</pubDate>
            </item>
            </channel>
</rss>