ในตอนที่ผ่านมา เราได้สร้างหน้าตาของแอพลิเคชัน Text Editor กันแล้ว ในตอนนี้เราจะทำความเข้าใจ Lifecycle ของแอพลิเคชันบน Windows 8 App Store กันให้มากขึ้น ก่อนจะเพิ่มความสามารถหลักของแอพลิเคชันนี้ นั่นคือ อ่านและเขียนไฟล์นั่นเอง
เดิมทีเมื่อเราพัฒนาแอพลิเคชันสำหรับพีซี แอพลิเคชันของเราจะเริ่มทำงานเมื่อผู้ใช้ (หรือระบบ) เรียกมันขึ้นมา จากนั้นแอพลิเคชันก็จะทำงานไปเรื่อยๆ จนกว่าผู้ใช้จะปิดแอพลิเคชัน หรือปิดเครื่องไป
อย่างไรก็ตาม การทำงานลักษณะดังกล่าวไม่เหมาะสมสำหรับแอพลิเคชันบน Windows 8 App Store ซึ่งต้องทำงานได้ดีทั้งบนพีซีและอุปกรณ์พกพาเช่นแท็บเล็ต เนื่องจากจะกระทบต่อประสิทธิภาพโดยรวมของระบบ และใช้พลังงานมากเกินไป
แอพลิเคชันสำหรับ Windows 8 App Store จะมีสามสถานะหลักๆ คือ
พฤติกรรมหลักๆ ของแอพลิเคชันจะเป็นดังนี้
สังเกตว่าเมื่อผู้ใช้สลับออกจากแอพลิเคชันของเราไปยังแอพลิเคชันอื่น มีโอกาสที่แอพลิเคชันของเราจะถูกระบบปิดได้โดยที่ผู้ใช้ไม่ได้สั่งปิดด้วยตนเอง อย่างไรก็ตามแอพลิเคชันของเราจะยังคงปรากฎอยู่ในแถบด้านซ้าย ดังนั้นผู้ใช้จะยังสลับมาใช้แอพลิเคชันของเรา (ซึ่งจริงๆ ถูกปิดไปแล้ว) ได้
เมื่อเกิดเหตุการณ์ลักษณะนี้ ผู้ใช้จะได้รับประสบการณ์ที่ไม่ดีนัก เนื่องจากแอพลิเคชันจะกลับไปเริ่มที่หน้าจอแรกใหม่ แทนที่จะเป็นหน้าล่าสุดที่ผู้ใช้เปิดไว้ ปัญหาที่แย่กว่านั้นคือหากผู้ใช้ทำงานค้างไว้ (เช่น กรอกแบบฟอร์ม) งานที่ผู้ใช้ทำค้างไว้ก็จะหายไปด้วย
เพื่อไม่ให้ผู้ใช้ต้องประสบปัญหาดังกล่าว เราจะต้องพัฒนาแอพลิเคชันให้เก็บสถานะสำคัญๆ ไว้ใน WinJS.Application.sessionState
เมื่อผู้ใช้สลับออกไปใช้แอพลิเคชันอื่น จากนั้นเมื่อผู้ใช้กลับมาเปิดแอพลิเคชันของเราอีกครั้ง หากเราตรวจพบว่าครั้งล่าสุดแอพลิเคชันถูกปิดโดยระบบ ให้แอพลิเคชันอ่านสถานะที่เก็บไว้กลับมาเพื่อทำให้แอพลิเคชันอยู่ในสถานะเดิม (หรือใกล้เคียง)
ในแอพลิเคชัน Text Editor ของเรา ผู้ใช้คงโวยวายเราเป็นแน่แท้หากพบว่าหลังจากเขาพิมพ์งาน แล้วสลับไปทำอย่างอื่นสักพัก จากนั้นกลับมาที่แอพลิเคชันของเราแล้วพบว่า ทุกสิ่งที่เขาได้พิมพ์ไปนั้นสูญหายไปแล้ว
ดังนั้นเราควรจะเก็บสถานะต่างๆ ของแอพลิเคชันไว้ โดยสิ่งที่เราจะเก็บมีดังนี้
อย่างไรก็ตาม โค้ดตั้งต้นของโปรเจกต์แบบ Navigation App ซึ่งเราสร้างไว้ตั้งแต่ต้นตอนที่แล้วนั้น ได้สร้างโค้ดสำหรับเก็บหน้าปัจจุบันที่ผู้ใช้เปิด รวมถึง Navigation Stack ทั้งหมด และคืนค่ากลับมาให้เมื่อผู้ใช้กลับมาใช้แอพลิเคชันหลังจากแอพลิเคชันถูกระบบปิดไปแล้วด้วย (ดูไฟล์ /js/default.js
) ดังนั้นส่วนนี้ถือว่าเสร็จแล้วโดยที่เราไม่ต้องทำอะไรเพิ่ม
ส่วนหน้า editor นั้นเราต้องทำเอง โดยแก้ไขไฟล์ /pages/editor/editor.js
เพื่อรอฟังอีเวนต์ checkpoint จาก WinJS.Application
ซึ่งจะถูกส่งออกมาในเวลาที่ผู้ใช้สลับแอพลิเคชัน ดังนี้
{syntaxhighlighter brush: jscript highlight:[9,10,11,12,13,14,15,21]}(function () {
"use strict";
var contentArea;
var bottomAppBar;
var saveButton;
var onClickSaveButton = function (event) {
bottomAppBar.winControl.hide();
};
var onCheckpoint = function () {
WinJS.Application.sessionState.editorState = {
innerText: contentArea.innerText,
selectionStart: contentArea.selectionStart,
selectionEnd: contentArea.selectionEnd
}
};
WinJS.UI.Pages.define("/pages/editor/editor.html", {
ready: function (element, options) {
contentArea = element.querySelector("#contentArea");
bottomAppBar = element.querySelector("#bottomAppBar");
saveButton = element.querySelector("#saveButton");
WinJS.Application.addEventListener("checkpoint", onCheckpoint);
saveButton.addEventListener("click", onClickSaveButton);
}
});
})();{/syntaxhighlighter}
โค้ดที่เพิ่มเข้ามาจะหาอีลิเมนต์ที่มีไอดี contentArea
(ซึ่งก็คือ Textarea ที่เราสร้างไว้ใน /pages/editor/editor.html
ตอนที่แล้ว) มาเก็บไว้ในตัวแปร contentArea จากนั้นรอฟังอีเวนต์ checkpoint จาก WinJS.Application
เมื่อเกิดอีเวนต์ขึ้นให้ทำงานในฟังก์ชัน onCheckpoint ซึ่งจะเก็บค่าต่างๆ ไว้ใน property editorState (เรากำหนดชื่อ property เอง) ให้กับวัตถุ WinJS.Application.sessionState
เท่านี้ก็เป็นอันเสร็จเรียบร้อย
จากนั้นเราจึงเพิ่มโค้ดในส่วน ready ให้ดึงข้อมูลจาก sessionState กลับมา โดยแก้ไขเพิ่มเติมฟังก์ชัน ready ในไฟล์ /pages/editor/editor.js ดังนี้
{syntaxhighlighter brush:jscript highlight:[6,7,8,9,10,11]} WinJS.UI.Pages.define("/pages/editor/editor.html", {
ready: function (element, options) {
contentArea = element.querySelector("#contentArea");
bottomAppBar = element.querySelector("#bottomAppBar");
saveButton = element.querySelector("#saveButton");
if (WinJS.Application.sessionState.editorState) {
var editorState = WinJS.Application.sessionState.editorState;
contentArea.innerText = editorState.innerText;
contentArea.setSelectionRange(editorState.selectionStart, editorState.selectionEnd);
delete WinJS.Application.sessionState.editorState;
}
saveButton.addEventListener("click", onClickSaveButton);
WinJS.Application.addEventListener("checkpoint", onCheckpoint);
}
});{/syntaxhighlighter}
จากนั้นทดสอบการเก็บสถานะของแอพลิเคชัน โดย
หากทุกอย่างถูกต้อง จะพบว่าแอพลิเคชันของเราจะเปิดขึ้นมาในหน้า editor พร้อมข้อความและ selection ที่เราทำไว้ก่อนหน้านี้แล้ว
สำหรับแอพลิเคชันบน Windows 8 App Store การทำงานกับส่วนต่างๆ ของระบบปฏิบัติการจะถูกควบคุมมากขึ้นกว่าเดิม โดยการใช้ฟีเจอร์ต่างๆ ของระบบปฏิบัติการนั้น จะต้องระบุฟีเจอร์ที่จะขออนุญาตใช้ไว้ในไฟล์ package.appmanifest ด้วย เหตุผลหลักๆ ก็คือเพื่อความปลอดภัยของผู้ใช้ ทำให้ผู้ใช้ทราบได้ตั้งแต่ก่อนติดตั้งว่าแอพลิเคชันนั้นจะทำอะไรบ้างนั่นเอง
สำหรับการอ่านและเขียนไฟล์ในแอพลิเคชันบน Windows 8 App Store ก็จะถูกจำกัดมากขึ้นเช่นกัน โดยเราจะไม่สามารถอ่านหรือเขียนไฟล์ใดๆ ก็ได้ในเครื่องตราบเท่าที่รู้ URI ของไฟล์ได้อีกต่อไป แต่จะอ่านและเขียนไฟล์ในเงื่อนไขต่อไปนี้ได้เท่านั้น:
ในแอพลิเคชันตัวอย่างของเราจะใช้เฉพาะการอ่านและเขียนไฟล์ผ่าน File Picker เท่านั้น
เราจะเริ่มจากแก้ไขหน้าแรกของแอพลิเคชันให้เปิดไฟล์ได้ โดยแก้ไขไฟล์ /pages/home/home.html ดังนี้
{syntaxhighlighter brush:jscript highlight:[6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,28,29]} (function () {
"use strict";
var onNewDocument = function (event) {
WinJS.Navigation.navigate("/pages/editor/editor.html");
};
var onOpenDocument = function (event) {
var currentState = Windows.UI.ViewManagement.ApplicationView.value;
if (currentState === Windows.UI.ViewManagement.ApplicationViewState.snapped) {
if (!Windows.UI.ViewManagement.ApplicationView.tryUnsnap()) {
return;
}
}
var openPicker = new Windows.Storage.Pickers.FileOpenPicker();
openPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.documentsLibrary;
openPicker.fileTypeFilter.replaceAll(["*"]);
openPicker.pickSingleFileAsync().then(function (selectedFile) {
if (selectedFile) {
WinJS.Navigation.navigate("/pages/editor/editor.html", {
file: selectedFile
});
}
});
};
WinJS.UI.Pages.define("/pages/home/home.html", {
ready: function (element, options) {
var newButton = element.querySelector("#newButton");
newButton.addEventListener("click", onNewDocument);
var openButton = element.querySelector("#openButton");
openButton.addEventListener("click", onOpenDocument);
}
});
})();{/syntaxhighlighter}
เนื่องจาก File Picker จะไม่สามารถทำได้หากหน้าจอแอพลิเคชันอยู่ใน Snapped View ดังนั้นเราจะต้องเขียนโค้ดเพื่อควบคุมตามลำดับดังนี้
ในตัวอย่างนี้เราจะไม่ได้หน้าแรกเปิดไฟล์ด้วยตัวเอง แต่จะให้ navigate ไปที่หน้า editor พร้อมทั้งส่งไฟล์ที่ผู้ใช้เลือกไปเป็น options ของหน้า editor เพื่อให้ทำหน้าที่เปิดไฟล์ต่อไป โดยการกำหนด options ของการ navigate นั้นทำได้โดยการส่งอาร์กิวเมนต์ตัวที่สองเพิ่มไปในฟังก์ชัน WinJS.Navigation.navigate
อย่างไรก็ตาม แอพลิเคชันของเราก็ยังไม่ได้เปิดไฟล์จริงๆ อยู่ดี เราจะต้องไปแก้ไขหน้า editor ให้ตรวจดูด้วยว่า หากหน้า editor ถูกเปิดโดยส่งไฟล์มาด้วย ก็ให้อ่านเนื้อหาของไฟล์นั้นมาแสดงผล ทำได้โดยแก้ไขฟังก์ชัน ready ของไฟล์ /pages/editor/editor.js ดังนี้ (ส่วนที่เพิ่มเติมคือบรรทัดที่เน้น)
{syntaxhighlighter brush:jscript highlight:[3,7,20,29,30,31,32,33,34,35]}(function () {
"use strict";
var pageTitle;
var contentArea;
var bottomAppBar;
var saveButton;
var currentFile;
var onClickSaveButton = function (event) {
bottomAppBar.winControl.hide();
}
var onCheckpoint = function () {
WinJS.Application.sessionState.editorState = {
innerText: contentArea.innerText,
selectionStart: contentArea.selectionStart,
selectionEnd: contentArea.selectionEnd
}
};
WinJS.UI.Pages.define("/pages/editor/editor.html", {
ready: function (element, options) {
pageTitle = element.querySelector("#pageTitle");
contentArea = element.querySelector("#contentArea");
bottomAppBar = element.querySelector("#bottomAppBar");
saveButton = element.querySelector("#saveButton");
if (WinJS.Application.sessionState.editorState) {
var editorState = WinJS.Application.sessionState.editorState;
contentArea.innerText = editorState.innerText;
contentArea.setSelectionRange(editorState.selectionStart, editorState.selectionEnd);
}
else if (options && options.file) {
Windows.Storage.FileIO.readTextAsync(options.file).then(function (fileContent) {
currentFile = options.file;
pageTitle.innerText = options.file.name;
contentArea.innerText = fileContent;
});
}
saveButton.addEventListener("click", onClickSaveButton);
WinJS.Application.addEventListener("checkpoint", onCheckpoint);
}
});
})();{/syntaxhighlighter}
โค้ดส่วนที่เพิ่มขึ้นมาได้เช็คว่าหากมีการส่งไฟล์มาด้วยขณะเรียกหน้านี้ (if (options && options.file)) ให้อ่านเนื้อหาของไฟล์ด้วยเอพีเอ Windows.Storage.FileIO เมื่อเสร็จแล้วแสดงเนื้อหา นอกจากนี้ยังอัพเดทชื่อของหน้านี้ให้ตรงกับชื่อไฟล์ และเก็บวัตถุ StorageFile ที่ส่งมาไว้ในตัวแปร currentFile ด้วย เพื่อใช้ในการบันทึกไฟล์ต่อไป
การบันทึกไฟล์ไม่ได้แตกต่างจากการอ่านไฟล์เท่าไหร่นัก เราจะมาเพิ่มฟีเจอร์ให้ปุ่ม Save สามารถบันทึกไฟล์ที่เปิดไว้ได้ หรือถ้ายังไม่เคยบันทึกไฟล์ไว้ก่อนเลยก็จะเรียก File Picker ขึ้นมาให้เลือกว่าจะบันทึกที่ใดได้ โดยแก้ไขฟังก์ชัน onClickSaveButton เดิม และเพิ่มฟังก์ชัน save ในไฟล์ /pages/editor/editor.js ดังนี้ (ส่วนที่เพิ่มเติมคือบรรทัดที่เน้น)
{syntaxhighlighter brush:jscript highlight:[1,2,3,4,5,6,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28]}var save = function (file, content) {
Windows.Storage.FileIO.writeTextAsync(file, content).done(function () {
currentFile = file;
pageTitle.innerText = file.name;
});
};
var onClickSaveButton = function (event) {
var content = contentArea.innerText;
if (currentFile) {
save(currentFile, content);
}
else {
var currentState = Windows.UI.ViewManagement.ApplicationView.value;
if (currentState === Windows.UI.ViewManagement.ApplicationViewState.snapped) {
if (!Windows.UI.ViewManagement.ApplicationView.tryUnsnap()) {
return;
}
}
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
savePicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.documentsLibrary;
savePicker.fileTypeChoices.insert("Plain Text", [".txt"]);
savePicker.suggestedFileName = "New Document";
savePicker.pickSaveFileAsync().then(function (selectedFile) {
if (selectedFile) {
save(selectedFile, content);
}
});
}
bottomAppBar.winControl.hide();
}{/syntaxhighlighter}
อย่างไรก็ตาม หากเราให้ผู้ใช้เลือกตำแหน่งบันทึกไฟล์เอง ผู้ใช้อาจจะเลือกบันทึกไฟล์ในเครื่องหรือบันทึกไฟล์บนกลุ่มเมฆอย่าง SkyDrive ก็ได้ ซึ่งโค้ดในการอ่านและเขียนไฟล์ทั้งหมดที่เราเขียนมานั้นสามารถให้ผู้ใช้บันทึกไฟล์บน SkyDrive ได้อยู่แล้ว แต่การบันทึกไฟล์อาจจะยังมีประสิทธิภาพไม่ดีเท่าที่ควร เนื่องจากโดยปกติวินโดวส์จะอัพโหลดไฟล์ขึ้นบน SkyDrive ทันทีที่เราเขียนไฟล์ ซึ่งอาจเกิดการอัพโหลดหลายครั้งหากการเขียนไฟล์ใช้เวลานานมากๆ ทำให้การบันทึกเสร็จช้า
ดังนั้น เราจะใช้ Windows.Storage.CachedFileManager ช่วยป้องกันไม่ให้ระบบอัพเดทไฟล์บน SkyDrive เพียงครั้งเดียวตอนที่เราเขียนทุกอย่างเสร็จแล้ว โดยเรียกฟังก์ชัน deferUpdates ก่อนเริ่มเขียนไฟล์ จากนั้นเมื่อเขียนไฟล์เสร็จจึงเรียกฟังก์ชัน completeUpdatesAsync เพื่อให้ระบบเริ่มอัพโหลดไฟล์ได้
จากแอพลิเคชันตัวอย่าง เราจะแก้ไขฟังก์ชัน save เป็นดังนี้
{syntaxhighlighter brush:jscript}var save = function (file, content) {
Windows.Storage.CachedFileManager.deferUpdates(file);
Windows.Storage.FileIO.writeTextAsync(file, content).done(function () {
Windows.Storage.CachedFileManager.completeUpdatesAsync(file).done(function (updateStatus) {
if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
currentFile = file;
pageTitle.innerText = file.name;
}
});
});
};{/syntaxhighlighter}
ถึงตอนนี้แอพลิเคชันของเราสามารถอ่านและเขียนไฟล์ได้แล้ว อย่างไรก็ตามเรายังต้องปรับปรุงการเก็บสถานะของแอพลิเคชันอีก เนื่องจากหากผู้ใช้เปิดไฟล์ขึ้นมา แล้วสลับไปใช้แอพลิเคชันอื่นจนกระทั่งแอพลิเคชันของเราถูกปิด เมื่อผู้ใช้กลับมาแก้ไขต่อจะบันทึกไฟล์ไม่ได้ เนื่องจากแอพลิเคชันของเราไม่ได้จำไว้ว่าไฟล์ที่เปิดคือไฟล์อะไร
ถึงตอนนี้เราอาจจะคิดว่าก็แก้ไขได้โดยการให้จำไฟล์นั้นไว้ใน SessionState ดังนี้
{syntaxhighlighter brush:jscript highlight:3}var onCheckpoint = function () {
WinJS.Application.sessionState.editorState = {
currentFile: currentFile
innerText: contentArea.innerText,
selectionStart: contentArea.selectionStart,
selectionEnd: contentArea.selectionEnd
};
};
{/syntaxhighlighter}
อย่างไรก็ตาม หากลองทดสอบดูจะพบว่าไม่ได้ผล โดยเมื่อเราอ่าน SessionState กลับมา จะพบว่าวัตถุ StorageFile ที่เก็บไว้กลายเป็นเพียงวัตถุธรรมดาไปแล้ว นั่นคือเราไม่สามารถถือวัตถุ StorageFile ข้ามเซสชันได้นั่นเอง
ในกรณีนี้ วิธีที่ถูกต้องคือให้เราเก็บวัตถุ SessionState ไว้กับ Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList ลิสต์นี้จะช่วยเรารักษาสิทธิ์การเข้าถึงไฟล์ข้ามเซสชันได้ ดังนี้
เราจะแก้ไขแอพลิเคชัน โดยเริ่มจากการให้เก็บวัตถุ StorageFile ลงใน futureAccessList เมื่อผู้ใช้จะสลับไปแอพลิเคชันอื่น ในฟังก์ชัน onCheckpoint ในไฟล์ /pages/editor/editor.html ดังนี้
{syntaxhighlighter brush:jscript highlight:[2,3,4]}var onCheckpoint = function () {
if (currentFile) {
Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList.addOrReplace("currentFile", currentFile);
}
WinJS.Application.sessionState.editorState = {
innerText: contentArea.innerText,
selectionStart: contentArea.selectionStart,
selectionEnd: contentArea.selectionEnd
}
};
{/syntaxhighlighter}
เนื่องจากแอพลิเคชันตัวอย่างนี้แก้ไขไฟล์ได้ทีละไฟล์เท่านั้น เราจึงกำหนด token ไว้ในซอร์สโค้ดเลยเพื่อความง่าย หากต้องการเก็บหลายๆ ไฟล์ เราอาจเปลี่ยนจากเมธอด addOrReplace ไปใช้เมธอด add แทน ซึ่งจะสร้าง token แบบสุ่มมาให้เรา จากนั้นจึงเอา token นั้นไปเก็บไว้ใน SessionState
จากนั้นแก้ไขแอพลิเคชันเพิ่มเติมให้เอาวัตถุ StorageFile กลับมาด้วย โดยแก้ไขฟังก์ชัน ready ในไฟล์ /pages/editor/editor.html ดังนี้
{syntaxhighlighter brush:jscript highlight:[11,12,13,14,15,16]}WinJS.UI.Pages.define("/pages/editor/editor.html", {
ready: function (element, options) {
contentArea = element.querySelector("#contentArea");
bottomAppBar = element.querySelector("#bottomAppBar");
saveButton = element.querySelector("#saveButton");
if (WinJS.Application.sessionState.editorState) {
var editorState = WinJS.Application.sessionState.editorState;
contentArea.innerText = editorState.innerText;
contentArea.setSelectionRange(editorState.selectionStart, editorState.selectionEnd);
delete WinJS.Application.sessionState.editorState;
if (Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList.containsItem("currentFile")) {
Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList.getFileAsync("currentFile").then(function (file) {
currentFile = file;
Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList.remove("currentFile");
});
}
}
else if (options && options.file) {
Windows.Storage.FileIO.readTextAsync(options.file).then(function (fileContent) {
contentArea.innerText = fileContent;
});
}
saveButton.addEventListener("click", onClickSaveButton);
WinJS.Application.addEventListener("checkpoint", onCheckpoint);
}
});
{/syntaxhighlighter}
เท่านี้ผู้ใช้ก็จะสามารถบันทึกไฟล์ได้ตามปกติถึงแม้แอพลิเคชันจะถูกระบบปิดได้แล้ว
ในตอนนี้เราได้พัฒนาฟีเจอร์ต่างๆ ให้แอพลิเคชันโดยคำนึงถึงข้อกำหนดต่างๆ สำหรับแอพลิเคชันบน Windows 8 App Store กันแล้ว ในตอนหน้าเราจะเพิ่มฟีเจอร์พิเศษต่างๆ เช่น การใช้ Charm Bar เพื่อให้แอพลิเคชันของเราสมบูรณ์ขึ้นต่อไป
บทความชุด การเขียนแอพลิเคชันสำหรับ Windows 8 App Store
Comments
เจ๋งครับ thank you
ข้อมูลเพิ่มเติมครับ
มีวิธีทำให้แอพทำงานเป็น Background task ได้นะครับ กรณีที่ต้องการให้งานบางอย่างยังคงทำอยู่หลังจากผู้ใช้สลับไปแอพอื่นแล้ว (ข้อมูลเพิ่มเติม [1][2])
ผมขัดใจมากตรงแอพมันไม่ทำงานตลอดนี่แหละ (T^T)
เป็น "ธรรมชาติ" ของ OS สมัยใหม่ครับ ไม่อย่างนั้นมันจัดการทรัพยากรลำบาก ยิ่งคำนึงเรื่องประหยัดไฟด้วยแล้วเรื่องนี้จำเป็นมาก
lewcpe.com, @wasonliw
ตรงนี้เข้าใจครับ แต่แม้กระทั่ง Skype ผมยังต้องใช้เวลาโหลดเพื่อเปิดและเข้าระบบร่วม 10 วินาที แอพปกติก็เกือบจะ 5+ วินาทีทั้งหมด ประสบการณ์ทำงานผมร่วงหมดเลยครับ อันนี้ไม่แน่ใจว่าเพราะคอมผมเก่าไปหรือเปล่า? (5 ปี+) ดังนั้นผมจึงแทบไม่ได้แตะแอพเท่าไหร่เลย แต่อย่างน้อยหน้า Desktop มันก็เร็วกว่าและแบตอึดกว่า Windows 7
เข้าใจว่าเก่านะครับ ผมแอพต่ำกว่า 5 วิ ใช้ ผมใช้ notebook i5 gen 1
SSD ด้วยหรือเปล่าครับ? ผมเปิด gMap เมื่อกี้ เกิน 15 วินาทีเลยทีเดียว -*- ทั้ง ๆ ที่หน้า Desktop นี่เร็วปกติเลย
จริง ๆ ต่ำกว่า 5 วิผมก็ว่าช้ากว่า iPad แล้วมั้งครับ (ไม่แน่ใจ อยากจับเล่นสองตัวเทียบดูเหมือนกัน)