ایجاد و توزیع برنامههای جدید AngularJS به همراه تمام وابستگیهای آنها و همچنین رعایت بهترین تجربههای کاری آن، اندکی مشکل است. به همین جهت تیم Angular برنامهای را به نام Angular CLI تدارک دیدهاست که تمام این مراحل را به سادگی هرچه تمامتر مدیریت میکند. ممکن است قالبهای زیادی را در مورد شروع به کار با AngularJS 2.0+ در وب پیدا کنید؛ اما هیچکدام از آنها تمام قابلیتهای Angular CLI را ارائه نمیدهند و همواره چندین قدم عقبتر از تیم Angular هستند. به همین جهت در طی یک سری قصد داریم قابلیتهای گوناگون این ابزار را بررسی کنیم.
Angular CLI چیست؟
ایجاد برنامههای جدید Angular لذت بخش هستند؛ اما ایجاد برنامههایی که از بهترین تجربههای کاری توصیه شدهی توسط تیم Angular پیروی میکنند، به همراه Unit tests هستند و همچنین برای توزیع بهینه سازی شدهاند، بسیار چالش برانگیز میباشند. به همین جهت برنامهی خط فرمانی به نام Angular CLI برای مدیریت این مسایل توسط تیم Angular ایجاد شدهاست، تا توسعه دهندگان بیشتر وقت خود را صرف بهینه سازی کدهای خود کنند تا اینکه درگیر تدارک مسایل جانبی این فریم ورک باشند.
اگر به پروژههای سورس باز ارائه شدهی جهت شروع کار با +AngularJS 2.0 دقت کنید، تعداد بیشماری پروژهی seed، قالبهای آماده، کدساز و غیره را خواهید یافت. اکثر آنها تفاوتهای قابل ملاحظهای را با یکدیگر داشته و در اغلب موارد بهترین تجربههای کاری Angular را نیز رعایت نمیکنند. برای مثال خبری از style guide آن و یا مباحث بهینه سازی ساخت و توزیع لحاظ شدهی در نگارشهای جدید Angular، در آنها نیست.
در اینجا بود که تیم Angular تصمیم گرفت تا در جهت ساماندهی به این وضعیت آشفته، برنامهی Angular CLI را ایجاد کند تا برنامه نویسها به همراه ابزاری باشند که بر اساس بهترین تجربههای کاری Angular تهیه شدهاست؛ سبب ایجاد برنامههایی خواهد شد که یکدست به نظر میرسند و همچنین همواره آخرین تغییرات توزیع و آزمایش برنامهها را نیز به همراه دارد.
پیشنیازهای نصب Angular CLI
پیش از شروع به نصب Angular CLI باید مطمئن شوید که آخرین نگارش NodeJS را نصب کردهاید. برای این منظور خط فرمان را گشوده و دستور ذیل را صادر کنید:
C:\>node -v v5.10.1
اگر این مطلب را در چند ماه بعد پس از نگارش آن مطالعه میکنید، به پروژهی Angular CLI مراجعه کرده و قسمت Prerequisites مستندات ابتدایی آنرا برای مشاهدهی آخرین نگارش NodeJS مورد نیاز آن، بررسی کنید.
نصب Angular CLI
پس از نصب پیشنیاز آن، اکنون خط فرمان را گشوده و دستور ذیل را صادر کنید:
C:\>npm install -g @angular/cli
پس از نصب آن، جهت اطمینان از عملیات انجام شده، دستور ذیل را در خط فرمان صادر کنید:
C:\>npm list -g @angular/cli --depth=0
C:\>npm list -g @angular/cli --depth=0 C:\Users\Vahid\AppData\Roaming\npm `-- @angular/cli@1.0.0
و همچنین برای مشاهدهی نگارش CLI نصب شده، دستور ذیل را اجرا نمائید:
C:\>ng -v _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ @angular/cli: 1.0.0 node: 6.10.2 os: win32 x64
ایجاد یک برنامهی جدید توسط Angular CLI
پس از نصب Angular CLI، اکنون میتوان از آن جهت ساخت یک برنامهی جدید Angular استفاده کرد. برای این منظور یک پوشهی جدید را ایجاد کرده و سپس از طریق خط فرمان به آن وارد شده (نگه داشتن دکمهی shift و سپس کلیک راست و انتخاب گزینهی Open command window here) و دستور ذیل را صادر کنید:
> ng new ngtest --skip-install
در اینجا ساختار یک پروژهی جدید Angular را مشاهده میکنید.
فایل | توضیحات |
.angular-cli.json | تنظیمات cli را به همراه دارد. |
editorconfig | مربوط به تنظیمات VSCode است. |
karma.conf.js | برای انجام unit tests است. |
package.json | وابستگیهای npm برنامه را به همراه دارد (که در زمان نگارش این مطلب تنظیمات Angular 4 را به همراه دارد). |
protractor.conf.js | برای اجرای آزمونهای end to end که در اینجا e2e نام گرفتهاست، میباشد. |
tsconfig.json | تنظیمات کامپایلر TypeScript را به همراه دارد. |
tslint.json | جهت اجرای Lint و ارائهی بهترین تجربههای کاری با TypeScript است. |
داخل پوشهی src، فایلهای اصلی پروژه قرار دارند:
- فایل index.html کار ارائه و شروع برنامه را انجام میدهد.
- فایل main.ts نقطهی آغاز برنامه است.
با توجه به استفادهی از پارامتر skip-install، هنوز وابستگیهای فایل package.json نصب نشدهاند. برای این منظور به پوشهی اصلی پروژه وارد شده (جایی که پوشهی ngtest و فایل package.json قرار دارد) و دستور npm install را صادر کنید تا وابستگیهای برنامه نیز دریافت شوند. البته اگر از پارامتر یاد شده استفاده نمیشد، اینکار به صورت خودکار توسط ng new انجام میگرفت.
>npm install
به این ترتیب وابستگیهای پروژه در پوشهی node_modules تشکیل خواهند شد.
به روز رسانی Angular CLI
روش به روز رسانی AngularCLI شامل این مراحل است:
الف) به روز رسانی بستهی عمومی نصب شدهی آن
npm uninstall -g @angular/cli npm cache clean npm install -g @angular/cli@latest
ب) به روز رسانی یک برنامهی محلی
در ادامه به پوشهی برنامهی خود وارد شده و دستورات ذیل را اجرا کنید:
rm rmdir /S/Q node_modules dist npm install --save-dev @angular/cli@latest npm install
مورد «الف» را به ازای هر نگارش جدید CLI، تنها یکبار باید انجام داد. اما مورد «ب» به ازای هر پروژهی موجود باید یکبار انجام شود (که سریعترین روش به روز رسانی وابستگیهای یک برنامه، به آخرین نگارش Angular است).
البته Angular به همراه دایرکتیو ویژهای به نام ngModel است که two-way data binding را با import ماژول ویژهی فرمها میسر میکند:
که آن نیز در اصل از جمع Property Binding و Event Binding تشکیل شدهاست:
<input [ngModel]="username" (ngModelChange)="username = $event">
<input [(ngModel)]='username' />
انقیاد به خواص یا Property binding
فرض کنید دو کامپوننت والد و فرزند را ایجاد کردهایم:
در کامپوننت والد، مقداری را توسط متد deposit هربار 100 آیتم افزایش میدهیم:
import { Component, OnInit } from "@angular/core"; @Component({ selector: "app-parent", templateUrl: "./parent.component.html", styleUrls: ["./parent.component.css"] }) export class ParentComponent implements OnInit { amount = 500; constructor() { } ngOnInit() { } deposit() { this.amount += 100; } }
<h2>Custom two way data binding</h2> <div class="panel panel-primary"> <div class="panel-heading"> <h2 class="panel-title">Parnet Component</h2> </div> <div class="panel-body"> <label>Available amount:</label> {{amount}} <button (click)="deposit()" class="btn btn-success">Deposit 100</button> <div> <app-child [amount]="amount"> </app-child> </div> </div> </div>
کامپوننت فرزند به صورت ذیل تعریف میشود:
import { Component, OnInit, Input } from "@angular/core"; @Component({ selector: "app-child", templateUrl: "./child.component.html", styleUrls: ["./child.component.css"] }) export class ChildComponent implements OnInit { @Input() amount: number; constructor() { } ngOnInit() { } withdraw() { this.amount -= 100; } }
با این قالب:
<div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">Child Component</h2> </div> <div class="panel-body"> <label>Amount available: </label> {{amount}} <button (click)="withdraw()" class="btn btn-danger">Withdraw 100</button> </div> </div>
در اینجا زمانیکه data binding را به صورت ذیل تعریف میکنیم:
<app-child [amount]="amount"> </app-child>
این ارتباط نیز یک طرفهاست. برای مثال اگر بر روی دکمهی Deposit والد کلیک کنیم:
مقدار افزایش یافتهی در والد، به فرزند نیز منتقل میشود و نمایش داده خواهد شد. اما اگر بر روی دکمهی withdraw فرزند کلیک کنیم:
تغییر صورت گرفته، به والد انعکاس پیدا نمیکند. برای اطلاع رسانی به والد، به انقیاد به رخدادها نیاز داریم.
انقیاد به رخدادها یا Event binding
یک کامپوننت میتواند به رخدادهای صادر شدهی توسط کامپوننتی دیگر گوش فرا دهد:
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core"; @Component({ selector: "app-child", templateUrl: "./child.component.html", styleUrls: ["./child.component.css"] }) export class ChildComponent implements OnInit { @Input() amount: number; @Output() amountChange = new EventEmitter(); constructor() { } ngOnInit() { } withdraw() { this.amount -= 100; this.amountChange.emit(this.amount); } }
اکنون در قالب کامپوننت والد، این رخداد را درون یک () معرفی خواهیم کرد:
<app-child [amount]="amount" (amountChange)="this.amount= $event"> </app-child>
اکنون اگر برنامه را آزمایش کنیم، با کلیک بر روی دکمهی withdraw فرزند، مقدار کاهش یافته به والد نیز منعکس میشود:
پیاده سازی syntax ویژهی Banana in a box
تا اینجا پیاده سازی two way data-binding سفارشی به پایان میرسد. اما تعریف طولانی:
<app-child [amount]="amount" (amountChange)="this.amount= $event"> </app-child>
<app-child [(amount)]="amount"> </app-child>
نکتهی ویژهی آن، وجود پسوند Change در نام رخداد تعریف شدهاست:
@Input() amount: number; @Output() amountChange = new EventEmitter();
بنابراین به صورت خلاصه جهت تعریف یک انقیاد دو طرفه سفارشی:
- ابتدا باید انقیاد به یک خاصیت ورودی x را تعریف کرد.
- سپس نیاز است انقیاد به یک رخداد خروجی همنام، که نام آن، پسوند Change را اضافهتر دارد، یعنی xChange را تعریف کرد.
- اکنون میتوان two-way data binding syntax ویژهای را به نام banana in a box بر روی ایندو تعریف کرد[(x)].
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
$.ajax({ url: "/login", // web.config --> appConfiguration -> tokenPath data: { grant_type: "refresh_token", refresh_token: refreshToken }, type: 'POST', // POST `form encoded` data contentType: 'application/x-www-form-urlencoded' })
doRefreshToken(refreshToken: string): Observable<any> { const body = new HttpParams() .set('grant_type', "refresh_token") .set('refresh_token', refreshToken); return this.http.post('/login', body.toString(), { headers: new HttpHeaders() .set('Content-Type', 'application/x-www-form-urlencoded') } ); }
Fixed In This Release of Visual Studio 2019 version 16.5
- Team Explorer not loading after update to mandatory latest visual studio version for Visual studio 2019
- Find Highlighting Fails when Matching with Match Case Disabled and Regex Option Enabled
Security Advisory Notice
CVE-2020-1108 .NET Core Denial of Service Vulnerability
A remote unauthenticated attacker could exploit this vulnerability by issuing specially crafted requests to the .NET Core application. The security update addresses the vulnerability by correcting how the .NET Core web application handles web requests.
CVE-2020-1161 .NET Core Denial of Service Vulnerability
A remote unauthenticated attacker could exploit this vulnerability by issuing specially crafted requests to the ASP.NET Core application. The security update addresses the vulnerability by correcting how the ASP.NET Core web application handles web requests.
از آنجا که یک کد مدیریت نشده، مبحث کارهای JIT را ندارد، ولی CLR مجبور است وقتی را برای آن بگذارد، به نظر میرسد ما با یک نقص کوچک در کارآیی روبرو هستیم. گفتیم که جیت کدها را در حافظهی پویا ذخیره میکند. به همین خاطر با terminate شدن یا خاتمه دادن به برنامه، این کدها از بین میروند یا اینکه اگر دو نمونه از برنامه را اجرا کنیم، هر کدام جداگانه کد را تولید میکنند و هر کدام برای خودشان حافظهای بر خواهند داشت و اگر مقایسهای با کدهای مدیریت نشده داشته باشید، در مورد مصرف حافظه یک مشکل ایجاد میکند. همچنین JIT در حین تبدیل به کدهای بومی یک بهینه سازی روی کد هم انجام میدهد که این بهینه سازی وقتی را به خود اختصاص میدهد ولی همین بهینه سازی کد موجب کارآیی بهتر برنامه میگردد.
در زبان سی شارپ دو سوئیچ وجود دارند که بر بهینه سازی کد تاثیر گذار هستند؛ سوئیچهای debug و optimize. در جدول زیر تاثیر هر یک از سوئیچها را بر کیفیت کد IL و JIT در تبدیل به کد بومی را نشان میدهد.
موقعیکه از دستور -optimize استفاده میشود، کد IL تولید شده شامل تعداد زیادی از دستورات بدون دستورالعمل No Operation یا به اختصار NOP و پرشهای شاخهای به خط کد بعدی میباشد. این دستور العملها ما را قادر میسازند تا ویژگی edit & Continue را برای دیباگ کردن و یک سری دستورالعملها را برای کدنویسی راحتتر برای دیباگ کردن و ایجاد break pointها داشته باشیم.
موقعی که کد IL بهینه شده تولید شود، این خصوصیات اضافه حذف خواهند شد و دنبال کردن خط به خط کد، کار سختی میشود. ولی در عوض فایل نهایی exe یا dll، کوچکتر خواهد شد. بهینه سازی IL توسط JIT حذف خواهد شد و برای کسانی که دوست دارند کدهای IL را تحلیل و آنالیز کنند، خواندنش سادهتر و آسانتر خواهد بود.
نکتهی بعدی اینکه موقعیکه شما از سوئیچ (/debug(+/full/pdbonly استفاده میکنید، یک فایل PDB یا Program Database ایجاد میشود. این فایل به دیباگرها کمک میکند تا متغیرهای محلی را شناسایی و به کدهای IL متصل شوند. کلمهی full بدین معنی است که JIT میتواند دستورات بومی را ردیابی کند تا مبداء آن کد را پیدا کند. سبب میشود که ویژوال استودیو به یک دیباگر متصل شده تا در حین اجرای پروسه، آن را دیباگ کند. در صورتی که این سوئیچ را استفاده نکنید، به طور پیش فرض پروسه اجرا و مصرف حافظه کمتر میشود. اگر شما پروسهای را اجرا کنید که دیباگر به آن متصل شود، به طور اجباری JIT مجبور به انجام عملیات ردیابی خواهد شد؛ مگر اینکه گزینهی suppress jit optimization on module load را غیرفعال کرده باشید.
موقعیکه در ویژوال استودیو دو حالت دیباگ و ریلیز را انتخاب میکنید، در واقع تنظیمات زیر را اجرا میکنید:
//debug /optimize /debug:full //======================= //Release /optimize+ /debug:pdbonly
اگر خیلی شک دارید که واقعا یک برنامهی CLR کارآیی یک برنامه را پایین میآورد، بهتر هست به بررسی کارآیی چند برنامه غیر آزمایشی noTrial که حتی خود مایکروسافت آن برنامهها را ایجاد کرده است بپردازید و آنها را با یک برنامهی unmanaged مقایسه کنید. قطعا باعث تعجب شما خواهد شد. این نکته دلایل زیادی دارد که در زیر تعدادی از آنها را بررسی میکنیم.
اینکه CLR در محیط اجرا قصد کمپایل دارد، باعث آشنایی کامپایلر با محیط اجرا میگردد. از این رو تصمیماتی را که میگیرد، میتواند به کارآیی یک برنامه کمک کند. در صورتیکه یک برنامهی unmanaged که قبلا کمپایل شده و با محیطهای متفاوتی که روی آنها اجرا میشود، هیچ آشنایی ندارد و نمیتواند از آن محیطها حداکثر بهرهوری لازم را به عمل آورد.
برای آشنایی با این ویژگیها توجه شما را به نکات ذیل جلب میکنم:
یک. JIT میتواند با نوع پردازنده آشنا شود که آیا این پردازنده از نسل پنتیوم 4 است یا نسل Core i. به همین علت میتواند از این مزیت استفاده کرده و دستورات اختصاصی آنها را به کار گیرد، تا برنامه با performance بالاتری اجرا گردد. در صورتی که unmanaged باید حتما دستورات را در پایینترین سطح ممکن و عمومی اجرا کند؛ در صورتیکه شاید یک دستور اختصاصی در یک سی پی یو خاص، در یک عملیات موجب 4 برابر، اجرای سریعتر شود.
دو. JIT میتواند بررسی هایی را که برابر false هستند، تشخیص دهد. برای فهم بهتر، کد زیر را در نظر بگیرید:
if (numberOfCPUs > 1) { ... }
سه. مورد بعدی که هنوز پیاده سازی نشده، ولی احتمال اجرای آن در آینده است، این است که یک کد میتواند جهت تصحیح بعضی موارد چون مسائل مربوط به دیباگ کردن و مرتب سازیهای مجدد، عمل کامپایل را مجددا برای یک کد اعمال نماید.
دلایل بالا تنها قسمت کوچکی است که به ما اثبات میکند که چرا CLR میتواند کارآیی بهتری را نسبت به زبانهای unmanaged امروزی داشته باشد. همچنین قولهایی از سازندگان برای بهبود کیفیت هر چه بیشتر این سیستمها به گوش میرسد.
کارآیی بالاتر
اگر برنامهای توسط شما بررسی شد و دیدید که نتایج مورد نیاز در مورد performance را نشان نمیدهد، میتوانید از ابزار کمکی که مایکروسافت در بستههای فریمورک دات نت قرار داده است استفاده کنید. نام این ابزار Ngen.exe است و وظیفهی آن این است که وقتی برنامه بر روی یک سیستم برای اولین مرتبه اجرا میگردد، کد همهی اسمبلیها را تبدیل کرده و آنها روی دیسک ذخیره میکند. بدین ترتیب در دفعات بعدی اجرا، JIT بررسی میکند که آیا کد کامپایل شدهی اسمبلی از قبل موجود است یا خیر. در صورت وجود، عملیات کامپایل به کد بومی لغو شده و از کد ذخیره شده استفاده خواهد کرد.
نکتهای که باید در حین استفاده از این ابزار به آن دقت کنید این است که کد در محیطهای واقعی اجرا چندان بهینه نیست. بعدا در مورد این ابزار به تفصیل صحبت میکنیم.
system.runtime.profileoptimization
کلاس بالا سبب میشود که CLR در یک فایل ثبت کند که چه متدهایی در حین اجرای برنامه کمپایل شوند تا در آینده در حین آغاز اجرای برنامه کامپایلر JIT بتواند همزمان این متدها را در ترد دیگری کامپایل کند. اگر برنامهی شما روی یک پردازندهی چند هستهای اجرا میشود، در نتیجه اجرای سریعتری خواهید داشت. به این دلیل که چندین متد به طور همزمان در حال کمپایل شدن هستند و همزمان با آماده سازی برنامه برای اجرا اتفاق میافتد؛ به جای اینکه عمل کمپایل همزمان با تعامل کاربر با برنامه باشد.
فرم اول کار نمایش فرم 2 را به عهده دارد.
فرم دوم کار ارسال ایمیل را انجام میدهد. این ایمیل نیز از طریق سرویس ذیل فراهم میشود:
namespace WinFormsIoc.Services { public interface IEmailsService { void SendEmail(string from, string to, string title, string message); } } namespace WinFormsIoc.Services { public class EmailsService : IEmailsService { public void SendEmail(string from, string to, string title, string message) { //todo: ... } } }
public partial class Form2 : Form { private readonly IEmailsService _emailsService; public Form2(IEmailsService emailsService) { _emailsService = emailsService; InitializeComponent(); }
var form2 = new Form2(ObjectFactory.GetInstance<IEmailsService>()); form2.Show();
var form2 = ObjectFactory.GetInstance<Form2>(); form2.Show();
مشکل! این دو راه حل هیچکدام به عنوان تزریق وابستگیها شناخته نمیشوند و به الگوی Service locator معروف هستند. مشکل آنها این است که کدهای ما در حال حاضر وابستگی مستقیمی به IoC container مورد استفاده پیدا کردهاند. در حالت اول ما خودمان دستی درخواست دادهایم که کدام وابستگی باید وهله سازی شود و در حالت دوم همانند حالت اول، کدهای ObjectFactory.GetInstance، مختص به یک IoC Container خاص است. نحوهی صحیح کار با IoC Containerها باید به این نحو باشد که یکبار در آغاز برنامه تنظیم شوند و در ادامه سایر کلاسهای برنامه طوری کار کنند که انگار IoC Container ایی وجود خارجی ندارد.
راه حل: ObjectFactory.GetInstance را کپسوله کنید.
using System.Windows.Forms; namespace WinFormsIoc.IoC { public interface IFormFactory { T Create<T>() where T : Form; } } using System.Windows.Forms; using StructureMap; namespace WinFormsIoc.IoC { public class FormFactory : IFormFactory { public T Create<T>() where T : Form { return ObjectFactory.GetInstance<T>(); } } }
using System; using System.Windows.Forms; using WinFormsIoc.IoC; namespace WinFormsIoc { public partial class Form1 : Form { private readonly IFormFactory _formFactory; public Form1(IFormFactory formFactory) { _formFactory = formFactory; InitializeComponent(); } private void btnShowForm2_Click(object sender, EventArgs e) { var form2 = _formFactory.Create<Form2>(); form2.Show(); } } }
نکتهی مهم این کدها عدم وابستگی مستقیم آن به هیچ نوع IoC Container خاصی است. این فرم اصلا نمیداند که IoC Container ایی در برنامه وجود دارد یا خیر.
مشکل! با تغییر سازندهی Form1 برنامه دیگر کامپایل نمیشود!
اگر فایل Program.cs را باز کنید، یک چنین سطری را دارد:
Application.Run(new Form1());
Application.Run(ObjectFactory.GetInstance<Form1>());
مثال کامل این بحث را از اینجا میتوانید دریافت کنید
WinFormsIoc.zip
DSL یا Domain Specific languages به معنی زبانهایی با دامنه محدود است که برای اهداف خاصی نوشته میشوند و تنها بر روی یک جنبه از هدف تمرکز دارند. این زبانها به شما اجازه نمیدهند که یک برنامه را به طور کامل با آن بنویسید. بلکه به شما اجازه میدهند به هدفی که برای آن نوشته شدهاند، برسید. یکی از این زبانها همان css هست که با آن کار میکنید. این زبان به صورت محدود تنها بر روی یک جنبه و آن، تزئین سازی المانهای وب، تمرکز دارد. در وقع مثل زبان سی شارپ همه منظوره نیست و محدودهای مشخص برای خود دارد. به این نوع از زبانهای DSL، نوع اکسترنال هم میگویند. چون زبانی مستقل برای خود است و به زبان دیگری وابستگی ندارد. ولی در یک زبان اینترنال، وابستگی به زبان دیگری وجود دارد. مثل Fluent Interfaceها که به ما شیوه آسانی از دسترسی به جنبههای یک شیء را میدهد. برای آشنایی هر چه بیشتر با این زبانها و ساختار آن، کتاب Domain Specific languages نوشته آقای مارتین فاولر توصیه میشود.
Groovy یک زبان شیء گرای DSL هست که برای پلتفرم جاوا ساخته شده است. برای اطلاعات بیشتر در مورد این زبان، صفحه ویکی ، میتواند مفید واقع شود.
از دیرباز سیستمهای Ant و Maven وجود داشتند و کار آنها اتوماسیون بعضی اعمال بود. ولی بعد از مدتی سیستم Gradle یا جمع کردن نقاط قوت آنها و افزودن ویژگیهای قدرتمندتری به خود، پا به میدان گذاشت تا راحتی بیشتری را برای برنامه نویس فراهم کند. از ویژگیهای گریدل میتوان داشتن زبان گرووی اشاره کرده که قدرت بیشتری را نسبت به سایر سیستمها داشت و مزیت مهم دیگر این بود که انعطاف بالایی را جهت افزودن پلاگینها داشت و گوگل با استفاده از این قابلیت، پشتیبانی از گریدل را در اندروید استادیو نیز گنجاند تا راحتی بیشتری را در اتوماسیون وظایف سیستمی ایجاد کند. در واقع آنچه شما در سیستم گریدل کار میکنید و اطلاعات خود را با آن کانفیگ میکنید، پلاگینی است که از سمت گوگل در اختیار شما قرار گرفته است و در مواقع خاص این وظایف توسط پلاگینها اجرا میشوند.
گریدل به راحتی از سایت رسمی آن قابل دریافت است و میتوان آن را در پروژههای جاوایی که مدنظر شماست، دریافت کنید و با استفاده از خط فرمان، با آن تعامل کنید. هر چند امروزه اکثر ویراستارهای جاوا از آن پشتیبانی میکنند.
گریدل یک ماهیت توصیفی دارد که شما تنها لازم است اعمالی را برای آن توصیف کنید تا بقیه کارها را انجام دهد. گریدل در پشت صحنه از یک "گراف جهت دار بدون دور" Directed Acycllic Graph یا به اختصار DAG استفاده میکند و طبق آن ترتیب وظایف یا taskها را دانسته و آنها را اجرا میکند. گریدل با این DAG، سه فاز آماده سازی، پیکربندی و اجرا را انجام میدهد.
- در مرحله آماده سازی ما به گریدل میگوییم چه پروژه یا پروژههایی نیاز به بیلد شدن دارند. در اندروید استادیو، این مرحله در فایل settings.gradle انجام میشود؛ شما در این فایل مشخص میکنید چه پروژههای نیاز به بیلد شدن توسط گریدل دارند. ساختار این فایل به این شکل است:
include ':ActiveAndroid-master', ':app', ':dbutilities'
-
در اولین مرحله انتظار دارد که فایل settings در دایرکتوری جاری باشد و اگر آن را پیدا کرد آن را مورد استفاده قرار میدهد؛ در غیر اینصورت مرحله بعدی را آغاز میکند.
- در مرحله دوم، در این دایرکتوری به دنبال دایرکتوری به نام master میگردد و اگر در آن هم یافت نکرد مرحله سوم را آغاز میکند.
- در مرحله سوم، جست و جو در دایرکتوری والد انجام میشود
- چنانچه این فایل را در هیچ یک از احتمالات بالا نیابد، همین پروژه جاری را تشخیص خواهد داد.
include ':ActiveAndroid-master', ':app', ':dbutilities' project('dbutilities').projectDir=new File(settingsDir,'../dir1/dir2');
-
در مرحله پیکربندی، وظایف یا taskها را معرفی میکنیم. این عمل پیکربندی توسط فایل build.gradle که برای پروژه اصلی و هر زیر پروژهای که مشخص شدهاند، صورت میگیرد. در این فایل شما میتوانید خواص و متدهایی را تعریف و و ظایفی را مشخص کنید.
در پروژه اصلی، فایل BuildGradle شامل خطوط زیر است:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.5.0' } } allprojects { repositories { jcenter() } }
در مرحله اجرا هم این وظایف را اجرا میکنیم. تمامی این سه عملیات توسط فایل و دستوری به نام gradlew که برگرفته از gradleWrapper میباشد انجام میشود. اگر در ترمینال اندروید استادیو این عبارت را تایپ کنید، میتوانید در ادامه دستور پیامهای مربوط به این عملیات را ببینید و ترتیب اجرای فازها را مشاهده کنید.
بیایید یک task را تعریف کنیم
task mytask <<{ println ".net tips task in config phase" }
gradlew mytask
gradlew --info mytask
اگر بخواهید خودتان دستی یک تسک پیکربندی را به یک تسک اجرایی تبدیل کنید، میتوانید متد doLast را صدا بزنید. کد زیر را توسط gradlew اجرا کنید؛ به همراه اطلاعات verbose تا ببینید که هر کدام از پیامها در کدام بخش چاپ میشوند. پیام اول در فاز پیکربندی و پیام دوم در فاز اجرایی چاپ میشوند.
task mytask { println ".net tips task in config phase" doLast{ println ".net tips task in exe phase" } }
یکی از کارهایی که در یک تسک میتوانید انجام دهید این است که آن را به یک تسک دیگر وابسته کنید. به عنوان مثال ما قصد داریم بعد از تسک mytask1، تسک my task2 اجرا شود و زمان پایان تسک mytask1 را در خروجی نمایش دهیم. برای اینکار باید بین تسکها یک وابستگی ایجاد شود و سپس با متد doLast کد خودمان را اجرایی نماییم. البته توجه داشته باشید که این وابستگیها تنها به تسکهای داخل فایل گریدل انجام میشود و نه تسکهای پلاگینها یا وابستگی هایی که تعریف میکنیم.
task mytask1 << { println ".net tips is the best" } task mytask2() { dependsOn mytask1 doLast{ Date time=Calendar.getInstance().getTime(); SimpleDateFormat formatter=new SimpleDateFormat("HH:mm:ss , YYYY/MM/dd"); println "mytask1 is done at " + formatter.format(time); } }
gradlew --info mytask2
Executing task ':app:mytask1' (up-to-date check took 0.003 secs) due to: Task has not declared any outputs. خروجی تسک شماره یک .net tips is the best :app:mytask1 (Thread[main,5,main]) completed. Took 0.046 secs. :app:mytask2 (Thread[main,5,main]) started. :app:mytask2 Executing task ':app:mytask2' (up-to-date check took 0.0 secs) due to: Task has not declared any outputs. خروجی تسک شماره دو mytask1 is done at 04:03:09 , 2016/07/07 :app:mytask2 (Thread[main,5,main]) completed. Took 0.075 secs. BUILD SUCCESSFUL
در گریدل مخالف doLast یعنی doFirst را نیز داریم ولی عملگر جایگزینی برای آن وجود ندارد و مستقیما باید آن را پیاده سازی کنید. خود گریدل به طور پیش فرض نیز تسکهای آماده ای نیز دارد که میتوانید در مستندات آن بیابید. به عنوان مثال یکی از تسکهای مفید و کاربردی آن تسک کپی کردن هست که از طریق آن میتوانید فایلی یا فایلهایی را از یک مسیر به مسیر دیگر کپی کنید. برای استفاده از چنین تسکهایی، باید تسکهای خود را به شکل زیر به شیوه اکشن بنویسید:
task mytask(type:Copy) { dependsOn mytask1 doLast{ from('build/apk') { include '**/*.apk' } into '.' } }
برای نمایش تسکهای موجود میتوانید از گریدل درخواست کنید که لیست تمامی تسکهای موجود را به شما نشان دهد. برای اینکار میتوانید دستور زیر را صدا کنید:
gradlew --info tasks
Other tasks ----------- clean jarDebugClasses jarReleaseClasses mytask mytask2 transformResourcesWithMergeJavaResForDebugUnitTest transformResourcesWithMergeJavaResForReleaseUnitTest
task mytask(type:Copy) { description "copy apk files to root directory" dependsOn mytask1 doLast{ from('build/apk') { include '**/*.apk' } into '.' } }
یکی دیگر از نکات جالب در مورد گریدل این است که میتواند برای شما callback ارسال کند. بدین صورت که اگر اتفاقی خاصی افتاد، تسک خاصی را اجرا کند. به عنوان مثال ما در کد پایین تسکی را ایجاد کردهایم که به ما این اجازه را میدهد، هر موقع تسکی در مرحله پیکربندی به بیلد اضافه میشود، تسک ما هم اجرا شود و نام تسک اضافه شده به بیلد را چاپ میکند.
tasks.whenTaskAdded{ task-> println "task is added $task.name" }
گریدل امکانات دیگری چون بررسی استثناءها و ایجاد استثناءها را هم پوشش میدهد که میتوانید در این صفحه آن را پیگیری کنید.
Gradle Wrapper
گریدل در حال حاضر مرتبا در حال تغییر و به روز رسانی است و اگر بخواهیم مستقیما با گریدل کار کنیم ممکن است که به مشکلاتی که در نسخه بندی است برخورد کنیم. از آنجا که هر پروژهای که روی سیستم شما قرار بگیرد از نسخهای متفاوتی از گریدل استفاده کند، باعث میشود که نتوانید نسخه مناسبی از گریدل را برای سیستم خود دانلود کنید. بدین جهت wrapper ایجاد شد تا دیگر نیازی به نصب گریدل پیدا نکنید. wrapper در هر پروژه میداند که که به چه نسخهای از گریدل نیاز است. پس موقعی که شما دستور gradlew را صدا میزنید در ویندوز فایل gradlew.bat صدا زده شده و یا در لینوکس و مک فایل شِل اسکریپت gradlew صدا زده میشود و wrapper به خوبی میداند که به چه نسخهای از گریدل برای اجرا نیاز دارد و آن را از طریق دانلود فراهم میکند. اگر همینک دایرکتوری والد پروژه اندرویدی خود را نگاه کنید میتوانید این دو فایل را ببینید.
از آنجا که خود اندروید استادیو به ساخت wrapper اقدام میکند، شما راحت هستید. ولی اگر دوست دارید خودتان برای پروژهای wrapper تولید کنید، مراحل زیر را دنبال کنید:
برای ایجاد wrapper توسط خودتان باید گریدل را دانلود و روی سیستم نصب کنید و سپس دستور زیر را صادر کنید:
gradle wrapper --gradle-version 2.4
اگر میخواهید ببینید wrapper که اندروید استادیو شما دارد چه نسخه از گردیل را صدا میزند مسیر را از دایرکتوری پروژه دنبال کنید و فایل زیر را بگشایید:
\gradle\wrapper\gradle-wrapper.properties
اینها فقط مختصراتی از آشنایی با نحوه عملکر گریدل برای داشتن دیدی روشنتر نسبت به آن بود. برای آشنایی بیشتر با گریدل، باید مستندات رسمی آن را دنبال کنید.