編輯:關於Android編程
www.eoeandroid.com首發,作者:[email protected]
大家都知道啟動速度慢是智能操作系統的一個通病,Android也不例外,啟動速度大概在1分鐘左右,雖然日本有一個叫quick boot的一秒啟動android的產品,但是畢竟是旁門左道。所以從常規來提高android的啟動速度成了大家研究的重點,也是難點。下面將初步研究的一下經驗跟大家分享一下。
首先看一下android系統的啟動流程:
bootloader
引導程序
kernel
內核
init
init初始化(這個大家都比較熟悉了,不要多說)
service manager
start services (啟動多個服務)從實際的測試數據來看,有兩個地方時最耗時間的,一個是zygote的裝載一千多個類和初始化堆棧的過程,用了20秒左右。另一個是掃描
/system/app,
/system/framework,
/data/app,
/data/app-private.
這幾個目錄下面的package用了大概10秒,所以我們重點能夠修理的就是這兩個老大的。
一、首先是調試工具的使用,可以測試哪些類和那些過程占用了多少時間,
主要工具為
stopwatch
Message loggers
grabserialbootchart 參考http://elinux.org/Bootchart 和
http://elinux.org/Bootchart
strace
AOSP的一部分(Eclair及以上版本)
使用例子
在init.rc中為了調試zygote
method tracer*
ftrace*
詳細使用可看提供的文檔和網頁介紹
上面的工具如果不用詳細的分析不一定都用到,也可以使用logcat就可以,在代碼中加一點計算時間和一些類的調試信息也可以達到很好效果。
二、zygote 裝載1千多個類
首先,我們可以添加一點調試信息,以獲得具體轉載情況。
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 404c513..f2b573c 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -259,6 +259,8 @@ public class ZygoteInit {
} else {
Log.i(TAG, Preloading classes...);
long startTime = SystemClock.uptimeMillis();
+ long lastTime = SystemClock.uptimeMillis();
+ long nextTime = SystemClock.uptimeMillis();
// Drop root perms while running static initializers.
setEffectiveGroup(UNPRIVILEGED_GID);
@@ -292,12 +294,24 @@ public class ZygoteInit {
if (Config.LOGV) {
Log.v(TAG, Preloading + line + ...);
}
+ //if (count%5==0) {
+ // Log.v(TAG, Preloading + line + ...);
+ //}
+ Log.v(TAG, Preloading + line + ...);
Class.forName(line);
+ nextTime = SystemClock.uptimeMillis();
+ if (nextTime-lastTime >50) {
+ Log.i(TAG, Preloading + line + ... took + (nextTime-lastTime) + ms.);
+ }
+ lastTime = nextTime;
+
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
if (Config.LOGV) {
Log.v(TAG,
GC at + Debug.getGlobalAllocSize());
}
+ Log.i(TAG,
+ GC at + Debug.getGlobalAllocSize());
runtime.gcSoftReferences();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
上面+代表添加的代碼,這樣就可以很容易的得到在裝載類的過程中具體裝載了哪些類,耗費了多久。具體裝載的類在文件platform/frameworks/base/ preloaded-classes
內容類似:
android.R$styleable
android.accounts.AccountMonitor
android.accounts.AccountMonitor$AccountUpdater
android.app.Activity
android.app.ActivityGroup
android.app.ActivityManager$MemoryInfo$1
android.app.ActivityManagerNative
android.app.ActivityManagerProxy
android.app.ActivityThread
android.app.ActivityThread$ActivityRecord
android.app.ActivityThread$AppBindData
android.app.ActivityThread$ApplicationThread
android.app.ActivityThread$ContextCleanupInfo
android.app.ActivityThread$GcIdler
android.app.ActivityThread$H
android.app.ActivityThread$Idler
而這個文件是由文件WritePreloadedClassFile.java中的WritePreloadedClassFile類自動生成
/**
* Writes /frameworks/base/preloaded-classes. Also updates
* {@link LoadedClass#preloaded} fields and writes over compiled log file.
*/
public class WritePreloadedClassFile
/**
* Preload any class that take longer to load than MIN_LOAD_TIME_MICROS us.
*/
static final int MIN_LOAD_TIME_MICROS = 1250;//這個代表了裝載時間小於1250us即1.25ms的類將不予裝載,也許可以改這個參數減少一下類的裝載
//這裡可以看到什麼樣的類會被裝載
A:啟動必須裝載的類,比如系統級的類
B:剛才說的裝載時間大於1.25ms的類
C:被使用一次以上或被應用裝載的類
仔細看看篩選類的具體實現,可以幫助我們認識哪些類比較重要,哪些可以去掉。
篩選規則是
第一 isPreloadable,
/**Reports if the given class should be preloaded. */
public static boolean isPreloadable(LoadedClass clazz) {
return clazz.systemClass && !EXCLUDED_CLASSES.contains(clazz.name);
}
意思是指除了EXCLUDED_CLASSES包含的類之外的所有系統裝載的類。
EXCLUDED_CLASSES包含
/**
* Classes which we shouldn't load from the Zygote.
*/
private static final Set
= new HashSet
// Binders
android.app.AlarmManager,
android.app.SearchManager,
android.os.FileObserver,
com.android.server.PackageManagerService$AppDirObserver,
// Threads
android.os.AsyncTask,
android.pim.ContactsAsyncHelper,
java.lang.ProcessManager
));
目前是跟Binders跟Threads有關的不會被預裝載。
第二 clazz.medianTimeMicros() > MIN_LOAD_TIME_MICROS裝載時間大於1.25ms。
第三 names.size() > 1 ,既是被processes一次以上的。
上面的都是指的system class,另外還有一些application class需要被裝載
規則是fromZygote而且不是服務
proc.fromZygote() && !Policy.isService(proc.name)
fromZygote指的除了com.android.development的zygote類
public boolean fromZygote() {
return parent != null && parent.name.equals(zygote)
&& !name.equals(com.android.development);
}
/除了常駐內存的服務
/**
* Long running services. These are restricted in their contribution to the
* preloader because their launch time is less critical.
*/
// TODO: Generate this automatically from package manager.
private static final Set
system_server,
com.google.process.content,
android.process.media,
com.android.bluetooth,
com.android.calendar,
com.android.inputmethod.latin,
com.android.phone,
com.google.android.apps.maps.FriendService, // pre froyo
com.google.android.apps.maps:FriendService, // froyo
com.google.android.apps.maps.LocationFriendService,
com.google.android.deskclock,
com.google.process.gapps,
android.tts
));
好了。要轉載的就是這些類了。雖然preloaded-classes是在下載源碼的時候已經確定了的,也就是對我們來說WritePreloadedClassFile類是沒用到的,我們可以做的就是在preloaded-classes文件中,把不預裝載的類去掉,試了把所有類去掉,啟動確實很快跳過那個地方,但是啟動HOME的時候就會很慢了。所以最好的方法就是只去掉那些沒怎麼用到的,不過要小心處理。至於該去掉哪些,還在摸索,稍後跟大家分享。有興趣的朋友可以先把preloaded-classes這個文件裡面全部清空,啟動快了很多,但在啟動apk的時候會慢了點。當然了,也可以把android相關的類全部去掉,剩下java的類,試過了也是可以提高速度。
三,系統服務初始化和package 掃描
在啟動系統服務的init2()時會啟動應用層(Java層)的所有服務。
public static void main(String[] args) {
System.loadLibrary(android_servers);
init1(args); //init1 初始化,完成之後會回調init2()
}
在init2()中會啟動一個線程來啟動所有服務
public static final void init2() {
Log.i(TAG, Entered the Android system server!);
Thread thr = new ServerThread();
thr.setName(android.server.ServerThread);
thr.start();
}
class ServerThread extends Thread {
。。。
public void run() {
。。。
關鍵服務:
ServiceManager.addService(entropy, new EntropyService());
ServiceManager.addService(Context.POWER_SERVICE, power);
context = ActivityManagerService.main(factoryTest);
ServiceManager.addService(telephony.registry, new TelephonyRegistry(context));
PackageManagerService.main(context,
factoryTest != SystemServer.FACTORY_TEST_OFF);//apk掃描的服務
ServiceManager.addService(Context.ACCOUNT_SERVICE,
new AccountManagerService(context));
ContentService.main(context,
factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL);
battery = new BatteryService(context);
ServiceManager.addService(battery, battery);
hardware = new HardwareService(context);
ServiceManager.addService(hardware, hardware);
AlarmManagerService alarm = new AlarmManagerService(context);
ServiceManager.addService(Context.ALARM_SERVICE, alarm);
ServiceManager.addService(Context.SENSOR_SERVICE, new SensorService(context));
WindowManagerService.main(context, power,
factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
上面這些都是關鍵服務,不建議進行裁剪。
下面的這些不是很關鍵,可以進行裁剪,當是必須相應的修改framework部分的代碼,工作量比較大和復雜。我去掉了20個服務,大概需要相應修改大概20多個文件。
statusBar = new StatusBarService(context);
ServiceManager.addService(statusbar, statusBar);
ServiceManager.addService(clipboard, new ClipboardService(context));
imm = new InputMethodManagerService(context, statusBar);
ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
ServiceManager.addService(netstat, new NetStatService(context));
connectivity = ConnectivityService.getInstance(context);
ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,
new AccessibilityManagerService(context));
notification = new NotificationManagerService(context, statusBar, hardware);
ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
ServiceManager.addService(mount, new MountService(context));
ServiceManager.addService(DeviceStorageMonitorService.SERVICE,
new DeviceStorageMonitorService(context));
ServiceManager.addService(Context.LOCATION_SERVICE, new LocationManagerService(context));
ServiceManager.addService( Context.SEARCH_SERVICE, new SearchManagerService(context) );
if (INCLUDE_DEMO) {
Log.i(TAG, Installing demo data...);
(new DemoThread(context)).start();
}
Intent intent = new Intent().setComponent(new ComponentName(
com.google.android.server.checkin,
com.google.android.server.checkin.CheckinService));
ServiceManager.addService(checkin, new FallbackCheckinService(context));
wallpaper = new WallpaperManagerService(context);
ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
headset = new HeadsetObserver(context);
dock = new DockObserver(context, power);
ServiceManager.addService(Context.BACKUP_SERVICE, new BackupManagerService(context));
ServiceManager.addService(Context.APPWIDGET_SERVICE, appWidget);
package 掃描部分,整個流程為下圖所示:
最終的zip文件(apk)讀取是在下面這兩個函數:
/*
* Open the specified file read-only. We memory-map the entire thing and
* close the file before returning.
*/
status_t ZipFileRO::open(const char* zipFileName)
{
int fd = -1;
off_t length;
assert(mFileMap == NULL);
LOGD(opening zip '%s' , zipFileName);
/*
* Open and map the specified file.
*/
fd = ::open(zipFileName, O_RDONLY);
if (fd < 0) {
LOGW(Unable to open zip '%s': %s , zipFileName, strerror(errno));
return NAME_NOT_FOUND;
}
length = lseek(fd, 0, SEEK_END);
if (length < 0) {
close(fd);
return UNKNOWN_ERROR;
}
mFileMap = new FileMap();
if (mFileMap == NULL) {
close(fd);
return NO_MEMORY;
}
if (!mFileMap->create(zipFileName, fd, 0, length, true)) {
LOGW(Unable to map '%s': %s , zipFileName, strerror(errno));
close(fd);
return UNKNOWN_ERROR;
}
mFd = fd;
/*
* Got it mapped, verify it and create data structures for fast access.
*/
if (!parseZipArchive()) {
mFileMap->release();
mFileMap = NULL;
return UNKNOWN_ERROR;
}
LOGD(done opening zip );
return OK;
}
/*
* Parse the Zip archive, verifying its contents and initializing internal
* data structures.
*/
bool ZipFileRO::parseZipArchive(void)
{
#define CHECK_OFFSET(_off) {
if ((unsigned int) (_off) >= maxOffset) {
LOGE(ERROR: bad offset %u (max %d): %s ,
(unsigned int) (_off), maxOffset, #_off);
goto bail;
}
}
const unsigned char* basePtr = (const unsigned char*)mFileMap->getDataPtr();
const unsigned char* ptr;
size_t length = mFileMap->getDataLength();
bool result = false;
unsigned int i, numEntries, cdOffset;
unsigned int val;
/*
* The first 4 bytes of the file will either be the local header
* signature for the first file (kLFHSignature) or, if the archive doesn't
* have any files in it, the end-of-central-directory signature
* (kEOCDSignature).
*/
val = get4LE(basePtr);
if (val == kEOCDSignature) {
LOGI(Found Zip archive, but it looks empty );
goto bail;
} else if (val != kLFHSignature) {
LOGV(Not a Zip archive (found 0x%08x) , val);
goto bail;
}
/*
* Find the EOCD. We'll find it immediately unless they have a file
* comment.
*/
ptr = basePtr + length - kEOCDLen;
while (ptr >= basePtr) {
if (*ptr == (kEOCDSignature & 0xff) && get4LE(ptr) == kEOCDSignature)
break;
ptr--;
}
if (ptr < basePtr) {
LOGI(Could not find end-of-central-directory in Zip );
goto bail;
}
/*
* There are two interesting items in the EOCD block: the number of
* entries in the file, and the file offset of the start of the
* central directory.
*
* (There's actually a count of the #of entries in this file, and for
* all files which comprise a spanned archive, but for our purposes
* we're only interested in the current file. Besides, we expect the
* two to be equivalent for our stuff.)
*/
numEntries = get2LE(ptr + kEOCDNumEntries);
cdOffset = get4LE(ptr + kEOCDFileOffset);
/* valid offsets are [0,EOCD] */
unsigned int maxOffset;
maxOffset = (ptr - basePtr) +1;
LOGV(+++ numEntries=%d cdOffset=%d , numEntries, cdOffset);
if (numEntries == 0 || cdOffset >= length) {
LOGW(Invalid entries=%d offset=%d (len=%zd) ,
numEntries, cdOffset, length);
goto bail;
}
/*
* Create hash table. We have a minimum 75% load factor, possibly as
* low as 50% after we round off to a power of 2.
*/
mNumEntries = numEntries;
mHashTableSize = roundUpPower2(1 + ((numEntries * 4) / 3));
mHashTable = (HashEntry*) calloc(1, sizeof(HashEntry) * mHashTableSize);
/*
* Walk through the central directory, adding entries to the hash
* table.
*/
ptr = basePtr + cdOffset;
for (i = 0; i < numEntries; i++) {
unsigned int fileNameLen, extraLen, commentLen, localHdrOffset;
const unsigned char* localHdr;
unsigned int hash;
if (get4LE(ptr) != kCDESignature) {
LOGW(Missed a central dir sig (at %d) , i);
goto bail;
}
if (ptr + kCDELen > basePtr + length) {
LOGW(Ran off the end (at %d) , i);
goto bail;
}
localHdrOffset = get4LE(ptr + kCDELocalOffset);
CHECK_OFFSET(localHdrOffset);
fileNameLen = get2LE(ptr + kCDENameLen);
extraLen = get2LE(ptr + kCDEExtraLen);
commentLen = get2LE(ptr + kCDECommentLen);
//LOGV(+++ %d: localHdr=%d fnl=%d el=%d cl=%d ,
// i, localHdrOffset, fileNameLen, extraLen, commentLen);
//LOGV( '%.*s' , fileNameLen, ptr + kCDELen);
/* add the CDE filename to the hash table */
hash = computeHash((const char*)ptr + kCDELen, fileNameLen);
addToHash((const char*)ptr + kCDELen, fileNameLen, hash);
// localHdr = basePtr + localHdrOffset;
// if (get4LE(localHdr) != kLFHSignature) {
// LOGW(Bad offset to local header: %d (at %d) ,
// localHdrOffset, i);
// goto bail;
// }
ptr += kCDELen + fileNameLen + extraLen + commentLen;
CHECK_OFFSET(ptr - basePtr);
}
result = true;
bail:
return result;
#undef CHECK_OFFSET
}
紅色部分是修改後的代碼,大家可以對比一下。(未完。。。)
做IOS開發的都知道,IOS提供了一個具有動態開關效果的UISwitch組件,這個組件很好用效果相對來說也很絢麗,當我們去點擊開關的時候有動畫效果,但遺憾的是Androi
初出茅廬 手動打包怎麼手動打包項目寫完了,現在需要把應用上傳到市場,問題出現—怎麼把代碼變成.apk(Android的可安裝文件)。1. 創建簽名文件2. 填
綜述Android中的事件分發機制也就是View與ViewGroup的對事件的分發與處理。在ViewGroup的內部包含了許多View,而ViewGroup繼承自View
首先,Activity是Android系統中的四大組件之一,可以用於顯示View。Activity是一個與用記交互的系統模塊,幾乎所有的 Activity都是和用戶進行交