android开机动画bootanimation
bootanim服务源码:frameworks/base/cmds/bootanimation/bootanim.rcservice bootanim /system/bin/bootanimationclass coreuser graphicsgroup graphics audiodisabledoneshot
bootanim服务可执行程序是/system/bin/bootanimation,定义为core类型的核心服务,所有者是graphics,组是graphics audio默认是disable的,即开机时不会自动启动,oneshot只启动一次,也就是该服务被异常销毁也不会重启。
之所以设置为disable是因为bootanim服务是依赖于系统的显示服务的,只有当显示相关的服务起来后才能启动也就是surfaceFlinger服务起来后会拉起bootanim服务显示开机动画。
surfaceFlinger服务源码:frameworks/native/services/surfaceflinger/surfaceflinger.rcservice surfaceflinger /system/bin/surfaceflingerclass coreuser systemgroup graphics drmrpc readproconrestart restart zygotewritepid /sys/fs/cgroup/stune/foreground/tasks
surfaceFlinger是负责显示相关的服务,是非常核心的服务必须一直保持存活的源码:frameworks/native/cmds/servicemanager/servicemanager.rcservice servicemanager /system/bin/servicemanagerclass coreuser systemgroup system readproccriticalonrestart restart healthdonrestart restart zygoteonrestart restart audioserveronrestart restart mediaonrestart restart surfaceflingeronrestart restart inputflingeronrestart restart drmonrestart restart cameraserverwritepid /dev/cpuset/system-background/tasks
init进程读取init.rc就会去解析服务,surfaceflinger也是定义的服务之一,调用 /system/bin/surfaceflinger启动surfaceflinger服务,服务的入口是main函数,下面对surfaceflinger进行分析:
源码:frameworks/native/services/surfaceflinger入口文件main_surfaceflinger.cppint main(int, char**) {signal(SIGPIPE, SIG_IGN);// When SF is launched in its own process, limit the number of// binder threads to 4.ProcessState::self()->setThreadPoolMaxThreadCount(4);//binder服务池大小限制,最多同时4个线程
// start the thread poolsp ps(ProcessState::self());ps->startThreadPool();//启动线程池监听连接
// instantiate surfaceflingersp flinger = new SurfaceFlinger();//新建一个实例
setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);//权限设置
set_sched_policy(0, SP_FOREGROUND); //调度设置
#ifdef ENABLE_CPUSETS// Put most SurfaceFlinger threads in the system-background cpuset// Keeps us from unnecessarily using big cores// Do this after the binder thread pool initset_cpuset_policy(0, SP_SYSTEM);#endif
// initialize before clients can connectflinger->init(); //初始化这里就会启动开机动画
// publish surface flingersp sm(defaultServiceManager());sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);
// publish GpuServicesp gpuservice = new GpuService();sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
// run surface flinger in this threadflinger->run();
return 0;}
源码:SurfaceFlinger.cppvoid SurfaceFlinger::init() {...// set initial conditions (e.g. unblank default device)initializeDisplays();//初始化显示设备
// start boot animationstartBootAnim();//启动开机动画}
void SurfaceFlinger::startBootAnim() {#ifdef MTK_AOSP_ENHANCEMENT// dynamic disable/enable boot animationcheckEnableBootAnim();#else// start boot animationproperty_set("service.bootanim.exit", "0"); //为了防止这个属性被设为1,不管现在值怎样都设置为0property_set("ctl.start", "bootanim"); //启动开机动画#endif}
下面分析init property service怎样将属性设置转为对应的动作在init.cpp中int main(int argc, char** argv) {...start_property_service(); //启动属性管理服务...}start_property_service函数在property_service.cpp中定义:start_property_servicevoid start_property_service() {property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,0666, 0, 0, NULL);if (property_set_fd == -1) {ERROR("start_property_service socket creation failed: %s\n", strerror(errno));exit(1);}listen(property_set_fd, 8);//socket监听binder服务请求,处理相关属性设置动作register_epoll_handler(property_set_fd,handle_property_set_fd); //epoll监听处理,处理回调函数是handle_property_set_fd}
handle_property_set_fdstatic void handle_property_set_fd(){...switch(msg.cmd) {...
if (!is_legal_property_name(msg.name, strlen(msg.name))) {ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);close(s);return;}getpeercon(s, &source_ctx);if(memcmp(msg.name,"ctl.",4) == 0) {// Keep the old close-socket-early behavior when handling// ctl.* properties.close(s);if (check_control_mac_perms(msg.value, source_ctx, &cr)) {...handle_control_message((char*) msg.name + 4, (char*) msg.value);} else {...}} else {if (check_mac_perms(msg.name, source_ctx, &cr)) {...property_set((char*) msg.name, (char*) msg.value); //普通属性,只是做设置值处理} else {}// Note: bionic's property client code assumes that the// property server will not close the socket until *AFTER*// the property is written to memory.close(s);}freecon(source_ctx);break;default:...}
handle_control_messagevoid handle_control_message(const std::string& msg, const std::string& name) {Service* svc = ServiceManager::GetInstance().FindServiceByName(name);//根据只有一个全局Service存储所有定义的服务,后面可以根据名字查找服务if (svc == nullptr) {ERROR("no such service '%s'\n", name.c_str());return;}if (msg == "start") {svc->Start(); //启动服务} else if (msg == "stop") {svc->Stop(); //停止服务} else if (msg == "restart") {svc->Restart();//重启服务} else {ERROR("unknown control msg '%s'\n", msg.c_str());}}
ServiceManager1.FindServiceByNameService* ServiceManager::FindServiceByName(const std::string& name) const {auto svc = std::find_if(services_.begin(), services_.end(),[&name] (const std::unique_ptr& s) {return name == s->name();});if (svc != services_.end()) {return svc->get();}return nullptr;}ServiceManager.h中定义services_向量数组std::vector> services_;

2.Startbool Service::Start() {...if (!seclabel_.empty()) {scon = seclabel_;} else {...rc = security_compute_create(mycon, fcon, string_to_security_class("process"),&ret_scon);...if (rc == 0 && scon == mycon) {ERROR("Service %s does not have a SELinux domain defined.\n", name_.c_str());//selinux安全检查,在init.rc中定义了服务不能启动会提示这个错误...}
pid_t pid = fork();if (pid == 0) {std::vector strs;for (const auto& s : args_) {strs.push_back(const_cast(s.c_str()));}strs.push_back(nullptr);if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) { //启动子进程执行服务程序ERROR("cannot execve('%s'): %s\n", args_[0].c_str(), strerror(errno));}_exit(127);}
NotifyStateChange("running");
}
NotifyStateChangevoid Service::NotifyStateChange(const std::string& new_state) const {if ((flags_ & SVC_EXEC) != 0) {// 'exec' commands don't have properties tracking their state.return;}std::string prop_name = StringPrintf("init.svc.%s", name_.c_str()); //init.svc.bootanimif (prop_name.length() >= PROP_NAME_MAX) {// If the property name would be too long, we can't set it.ERROR("Property name \"init.svc.%s\" too long; not setting to %s\n",name_.c_str(), new_state.c_str());return;}
property_set(prop_name.c_str(), new_state.c_str());//设置init.svc.bootanim 为running}

3. Stopvoid Service::Stop() {StopOrReset(SVC_DISABLED);}
void Service::StopOrReset(int how) {/* The service is still SVC_RUNNING until its process exits, but if it has* already exited it shoudn't attempt a restart yet. */flags_ &= ~(SVC_RESTARTING | SVC_DISABLED_START);
if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) {/* Hrm, an illegal flag. Default to SVC_DISABLED */how = SVC_DISABLED;}/* if the service has not yet started, prevent* it from auto-starting with its class*/if (how == SVC_RESET) {flags_ |= (flags_ & SVC_RC_DISABLED) ? SVC_DISABLED : SVC_RESET;} else {flags_ |= how;}
if (pid_) {//如果服务进程已经启动才会有pid_NOTICE("Service '%s' is being killed...\n", name_.c_str());kill(-pid_, SIGKILL);//杀死服务进程NotifyStateChange("stopping"); //设置init.svc.bootanim 为stopping} else {NotifyStateChange("stopped");//设置init.svc.bootanim 为stopped}}
4.Restartvoid Service::Restart() {if (flags_ & SVC_RUNNING) {/* Stop, wait, then start the service. */StopOrReset(SVC_RESTART);} else if (!(flags_ & SVC_RESTARTING)) {/* Just start the service since it's not running. */Start();} /* else: Service is restarting anyways. */}
经过上面分析,我们已经知道了开机init进程解析init.rc注册bootanim服务到surfaceFlinger服务启动startBootanim到属性服务如何将属性转化为对应的动作即启动子进程执行 /system/bin/bootanimation整个过程,下面分析bootanimation程序是怎样启动动画的
bootanimation源码:frameworks/base/cmds/bootanimation/入口bootanimation_main.cppint main(int argc, char** argv){setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
char value[PROPERTY_VALUE_MAX];property_get("debug.sf.nobootanimation", value, "0"); //调试状态可通过设置属性debug.sf.nobootanimation值为1禁止开机动画int noBootAnimation = atoi(value);ALOGI_IF(noBootAnimation, "boot animation disabled");if (!noBootAnimation) {sp proc(ProcessState::self());ProcessState::self()->startThreadPool();
bool setBoot = true;bool setRotated = false;bool sePaly = true;if(argc > 1){if(!strcmp(argv[1],"shut"))setBoot = false;}if(argc > 2){if(!strcmp(argv[2],"nomp3"))sePaly = false;}if(argc > 3){if(!strcmp(argv[3],"rotate"))setRotated = true;}ALOGD("[BootAnimation %s %d]setBoot=%d,sePaly=%d,setRotated=%d",__FUNCTION__,__LINE__,setBoot,sePaly,setRotated);char volume[PROPERTY_VALUE_MAX];property_get("persist.sys.mute.state", volume, "-1");//属性persist.sys.mute.state设置音量int nVolume = -1;nVolume = atoi(volume);ALOGD("[BootAnimation %s %d]nVolume=%d",__FUNCTION__,__LINE__,nVolume);if(nVolume == 0 || nVolume == 1 ){sePaly = false;}ALOGD("before new BootAnimation...");ALOGD("[BootAnimation %s %d]before new BootAnimation...",__FUNCTION__,__LINE__);sp boot = new BootAnimation(setBoot,sePaly,setRotated); //智能指针sp,使用后自动释放ALOGD("joinThreadPool...");ALOGD("[BootAnimation %s %d]before joinThreadPool...",__FUNCTION__,__LINE__);IPCThreadState::self()->joinThreadPool();//等待开机动画线程结束}ALOGD("[BootAnimation %s %s %d]end",__FILE__,__FUNCTION__,__LINE__);return 0;}
BootAnimation源码:BootAnimation.cppBootAnimation::BootAnimation(bool bSetBootOrShutDown, bool bSetPlayMP3,bool bSetRotated) : Thread(false), mZip(NULL){ALOGD("[BootAnimation %s %d]",__FUNCTION__,__LINE__);mSession = new SurfaceComposerClient();bBootOrShutDown = bSetBootOrShutDown;bShutRotate = bSetRotated;bPlayMP3 = bSetPlayMP3;mProgram = 0;bETC1Movie = false;mBootVideoPlayType = BOOT_VIDEO_PLAY_FULL;mBootVideoPlayState = MEDIA_NOP;ALOGD("[BootAnimation %s %d]bBootOrShutDown=%d,bPlayMP3=%d,bShutRotate=%d",__FUNCTION__,__LINE__,bBootOrShutDown,bPlayMP3,bShutRotate);}
在BootAnimation.h中,我们可以看到class BootAnimation : public Thread, public IBinder::DeathRecipient{...private:virtual bool threadLoop();virtual status_t readyToRun();virtual void onFirstRef();...}BootAnimation继承于Thread,而Thread继承RefBase类,并且BootAnimation覆写了onFirstRef,readyToRun,threadLoop函数,结合智能指针知识,在创建对象BootAnimation时候会首先调用onFirstRef()函数void BootAnimation::onFirstRef() {status_t err = mSession->linkToComposerDeath(this);ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));if (err == NO_ERROR) {run("BootAnimation", PRIORITY_DISPLAY);//这里会调用onFirstRef()函数}}
status_t BootAnimation::readyToRun() {if (bBootOrShutDown) {initBootanimationZip(); //开机动画} else {initShutanimationZip();//关机动画}if ((mZip != NULL)&&!(mZipFileName.isEmpty())) {ZipEntryRO desc = mZip->findEntryByName("desc.txt");//desc.txt文件描述了如何播放动画,如图片目录,播放次数和播放帧率uint16_t method;mZip->getEntryInfo(desc, &method, NULL, NULL, NULL, NULL, NULL);mZip->releaseEntry(desc);if (method == ZipFileRO::kCompressStored) {bETC1Movie = false;} else {bETC1Movie = true;}}mAssets.addDefaultAssets();
sp dtoken(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));DisplayInfo dinfo;status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);... //下面主要初始化Surface相关的类sp dtoken(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));DisplayInfo dinfo;status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);if (status)return -1;/// M: The tablet rotation maybe 90/270 degrees, so set the lcm config for tabletSurfaceComposerClient::setDisplayProjection(dtoken, DisplayState::eOrientationDefault, Rect(dinfo.w, dinfo.h), Rect(dinfo.w, dinfo.h));
// create the native surfacesp control = session()->createSurface(String8("BootAnimation"), //创建BootAnimation surfacedinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::openGlobalTransaction();control->setLayer(0x2000010);SurfaceComposerClient::closeGlobalTransaction();
sp s = control->getSurface();.../*opengl渲染相关初始化*/EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
ALOGD("initialize opengl and egl");EGLBoolean eglret = eglInitialize(display, 0, 0);
//下面是加密相关处理,部分厂家放在initBootanimationZip处理}

#define GLOBAL_DEVICE_BOOTANIM_OPTR_NAME "persist.operator.optr"#define REGIONAL_BOOTANIM_FILE_NAME "persist.bootanim.logopath"
void BootAnimation::initBootanimationZip() {
....f (property_get(GLOBAL_DEVICE_BOOTANIM_OPTR_NAME, OPTR, NULL) > 0){//获取bootanimation.zip文件的路径for(int i=0;i/*接下来对zip包进行解密*/property_get("vold.decrypt", decrypt, "");..}

threadLoop根据Thread的周期函数在调用完readyToRun函数后就会调用threadLoop()函数bool BootAnimation::threadLoop(){bool r;// We have no bootanimation file, so we use the stock android logo// animation.sp mediaplayer;//开机音乐设置,播放const char* resourcePath = initAudioPath();status_t mediastatus = NO_ERROR;if (resourcePath != NULL) {bPlayMP3 = true;ALOGD("sound file path: %s", resourcePath);mediaplayer = new MediaPlayer();mediastatus = mediaplayer->setDataSource(NULL, resourcePath, NULL);
sp listener = new BootVideoListener(this);mediaplayer->setListener(listener);
if (mediastatus == NO_ERROR) {ALOGD("mediaplayer is initialized");Parcel* attributes = new Parcel();attributes->writeInt32(AUDIO_USAGE_MEDIA); //usageattributes->writeInt32(AUDIO_CONTENT_TYPE_MUSIC); //audio_content_type_tattributes->writeInt32(AUDIO_SOURCE_DEFAULT); //audio_source_tattributes->writeInt32(0); //audio_flags_mask_tattributes->writeInt32(1); //kAudioAttributesMarshallTagFlattenTags of mediaplayerservice.cppattributes->writeString16(String16("BootAnimationAudioTrack")); // tagsmediaplayer->setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, *attributes);mediaplayer->setAudioStreamType(AUDIO_STREAM_MUSIC);mediastatus = mediaplayer->prepare();}if (mediastatus == NO_ERROR) {ALOGD("media player is prepared");mediastatus = mediaplayer->start();}
}else{bPlayMP3 = false;}
if ((mZip == NULL)&&(mZipFileName.isEmpty())) {//如果开机动画不存在的情况r = android(); //加载原生的ANDROID开机图片} else {if (!bETC1Movie) {ALOGD("threadLoop() movie()");r = movie(); //解析动画,原生开机动画,ANDROID} else {ALOGD("threadLoop() ETC1movie()");r = ETC1movie(); //解析动画,定制的开机动画,bootanimation.zip中desc.txt文件描述动画播放}}if (resourcePath != NULL) {if (mediastatus == NO_ERROR) {ALOGD("mediaplayer was stareted successfully, now it is going to be stoped");mediaplayer->stop();mediaplayer->disconnect();mediaplayer.clear();}}eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);eglDestroyContext(mDisplay, mContext);eglDestroySurface(mDisplay, mSurface);mFlingerSurface.clear();mFlingerSurfaceControl.clear();eglTerminate(mDisplay);IPCThreadState::self()->stopProcess();//停止线程return r;}
ETC1movie先看个desc.txt例子420 232 20p 1 0 part0p 0 10 part11355263
第一行表示420*232 20表示1秒播放多少帧(帧率)第二行p表示动画,也有用c表示的,1表示播放1次,0表示这部分播完到下部分的时间间隔,part0表示播放的图片目录第三行p(旧版本的)表示动画,也有用c表示(新版本),0表示无限循环播放,10表示两次播放两个part之间间隔的帧数是10,间隔时间是10*1/20秒,只作用于part1
bool BootAnimation::ETC1movie(){ZipEntryRO desc = mZip->findEntryByName("desc.txt"); //如果找不到desc.txt则无法播放动画ALOGE_IF(!desc, "couldn't find desc.txt");if (!desc) {return false;}...Animation animation;
// Parse the description filefor (;;) {const char* endl = strstr(s, "\n");if (!endl) break;String8 line(s, endl - s);const char* l = line.string();int fps, width, height, count, pause;char path[ANIM_ENTRY_NAME_MAX];char color[7] = "000000"; // default to black if unspecifiedchar pathType;if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {//解析desc.txt第一行ALOGD("> w=%d, h=%d, fps=%d", width, height, fps); // add loganimation.width = width;animation.height = height;animation.fps = fps;}else if (sscanf(l, "%c %d %d %s", &pathType, &count, &pause, path) == 4) {//解析desc.txt第一行后面的行ALOGD("> type=%c, count=%d, pause=%d, path=%s", pathType, count, pause, path); // add logAnimation::Part part;part.playUntilComplete = pathType == 'c';part.count = count;part.pause = pause;part.path = path;animation.parts.add(part);//添加值animation中if (!parseColor(color, part.backgroundColor)) {ALOGD("> invalid color '#%s'", color);part.backgroundColor[0] = 0.0f;part.backgroundColor[1] = 0.0f;part.backgroundColor[2] = 0.0f;}}
s = ++endl;}
/*读取对应的图片*/ZipEntryRO entry;char name[ANIM_ENTRY_NAME_MAX];while ((entry = mZip->nextEntry(cookie)) != NULL) {const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {ALOGE("Error fetching entry file name");continue;}
const String8 entryName(name);const String8 path(entryName.getPathDir());const String8 leaf(entryName.getPathLeaf());if (leaf.size() > 0) {for (size_t j=0 ; jmZip->endIteration(cookie);for (size_t i=0 ; i...//下面真正开始播放动画for (int r=0 ; !part.count || r//根据前面解析的part进行循环// Exit any non playuntil complete parts immediatelyif(exitPending() && !part.playUntilComplete) {//ALOGD("[BootAnimation %s %d]part.playUntilComplete=%d", __FUNCTION__, __LINE__ ,part.playUntilComplete);break;}glClearColor(part.backgroundColor[0],part.backgroundColor[1],part.backgroundColor[2],1.0f);for (size_t j=0 ; j//每个part图片播放const Animation::Frame& frame(part.frames[j]);nsecs_t lastFrame = systemTime();//ALOGD("[BootAnimation %s %d]i=%d,r=%d,j=%d,lastFrame=%lld(%lld ms),file=%s",__FUNCTION__,__LINE__,i,r,j,lastFrame,ns2ms(lastFrame),frame.name.string());if (r > 0) {glBindTexture(GL_TEXTURE_2D, frame.tid);} else {if (part.count != 1) {glGenTextures(1, &frame.tid);glBindTexture(GL_TEXTURE_2D, frame.tid);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);}initTexture(frame.fullPath.string());}
static GLfloat quadVertex[] = { -1.0f, 1.0f, 0.0f, // Position 00.0f, 0.0f, // TexCoord 0-1.0f, -1.0f, 0.0f, // Position 10.0f, 1.0f, // TexCoord 11.0f, -1.0f, 0.0f, // Position 21.0f, 1.0f, // TexCoord 21.0f, 1.0f, 0.0f, // Position 31.0f, 0.0f // TexCoord 3};
static GLushort quadIndex[] = { 0, 1, 2, 0, 2, 3 };3, GL_FLOAT,false, 5*sizeof(GL_FLOAT), quadVertex);
glVertexAttribPointer(mAttribTexCoord,2, GL_FLOAT,false, 5*sizeof(GL_FLOAT), &quadVertex[3]);glEnableVertexAttribArray(mAttribPosition);glEnableVertexAttribArray(mAttribTexCoord);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, quadIndex);eglSwapBuffers(mDisplay, mSurface);
nsecs_t now = systemTime();nsecs_t delay = frameDuration - (now - lastFrame);//ALOGD("[BootAnimation %s %d]%lld,delay=%lld",__FUNCTION__,__LINE__,ns2ms(now - lastFrame), ns2ms(delay));lastFrame = now;
if (delay > 0) {struct timespec spec;spec.tv_sec = (now + delay) / 1000000000;spec.tv_nsec = (now + delay) % 1000000000;int err;do {err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL); //播放的帧率} while (err<0 && errno == EINTR);}if(!bPlayMP3){checkExit();}else{if(mBootVideoPlayState == MEDIA_PLAYBACK_COMPLETE || mBootVideoPlayState == MEDIA_ERROR) {checkExit(); //检查是否需要退出动画}}}
usleep(part.pause * ns2us(frameDuration));//两个part之间的时间间隔
// For infinite parts, we've now played them at least once, so perhaps exitif(exitPending() && !part.count) {ALOGD("[BootAnimation %s %d]break,exitPending()=%d,part.count=%d",__FUNCTION__,__LINE__,exitPending(),part.count);break;}}// free the textures for this partif (part.count != 1) {for (size_t j=0 ; j














更多相关文章

  1. C语言函数以及函数的使用
  2. 补间动画--缩放动画XML
  3. 安卓控件属性
  4. 直播源码Android实现 曲线路径动画
  5. Android:EditText 常用属性

随机推荐

  1. Android(安卓)对话框【Dialog】去除白色
  2. Android并行计算
  3. android自动化测试工具
  4. Android(安卓)application 和 activity
  5. android 下载进度条的实现
  6. Android(安卓)- Rerofit-RxJava(转载)
  7. Android(安卓)上传图片到服务器(多文件上
  8. Android中彩信文件的读取
  9. android 权限介绍(一)
  10. android Toolbar的使用结合状态栏与返回