欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android Activity的啟動(dòng)過(guò)程源碼解析

 更新時(shí)間:2018年05月10日 09:01:52   作者:singwhatiwanna  
這篇文章主要介紹了Android Activity的啟動(dòng)過(guò)程源碼解析,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

前言

Activity是Android中一個(gè)很重要的概念,堪稱四大組件之首,關(guān)于Activity有很多內(nèi)容,比如生命周期和啟動(dòng)Flags,這二者想要說(shuō)清楚,恐怕又要寫兩篇長(zhǎng)文,更何況分析它們的源碼呢。不過(guò)本文的側(cè)重點(diǎn)不是它們,我要介紹的是一個(gè)Activity典型的啟動(dòng)過(guò)程,本文會(huì)從源碼的角度對(duì)其進(jìn)行分析。我們知道,當(dāng)startActivity被調(diào)用的時(shí)候,可以啟動(dòng)一個(gè)Activity,但是你知道這個(gè)Activity是如何被啟動(dòng)的嗎?每個(gè)Activity也是一個(gè)對(duì)象,你知道這個(gè)對(duì)象是啥時(shí)候被創(chuàng)建的嗎(也就是說(shuō)它的構(gòu)造方法是什么時(shí)候被調(diào)用的)?為什么onCreate是Activity的執(zhí)行入口?所有的這一切都被系統(tǒng)封裝好了,對(duì)我們來(lái)說(shuō)是透明的,我們使用的時(shí)候僅僅是傳遞一個(gè)intent然后startActivity就可以達(dá)到目的了,不過(guò),閱讀了本文以后,你將會(huì)了解它的背后到底做了哪些事情。在分析之前,我先介紹幾個(gè)類:

  1. Activity:這個(gè)大家都熟悉,startActivity方法的真正實(shí)現(xiàn)在Activity中
  2. Instrumentation:用來(lái)輔助Activity完成啟動(dòng)Activity的過(guò)程
  3. ActivityThread(包含ApplicationThread + ApplicationThreadNative + IApplicationThread):真正啟動(dòng)Activity的實(shí)現(xiàn)都在這里

源碼分析

首先看入口

code:Activity#startActivity

@Override 
public void startActivity(Intent intent) { 
 startActivity(intent, null); 
} 
 
@Override 
public void startActivity(Intent intent, Bundle options) { 
 if (options != null) { 
  startActivityForResult(intent, -1, options); 
 } else { 
  // Note we want to go through this call for compatibility with 
  // applications that may have overridden the method. 
  startActivityForResult(intent, -1); 
 } 
} 
 
public void startActivityForResult(Intent intent, int requestCode) { 
 startActivityForResult(intent, requestCode, null); 
} 

說(shuō)明:顯然,從上往下,最終都是由startActivityForResult來(lái)實(shí)現(xiàn)的

接著看

code:Activity#startActivityForResult

public void startActivityForResult(Intent intent, int requestCode, Bundle options) { 
 //一般的Activity其mParent為null,mParent常用在ActivityGroup中,ActivityGroup已廢棄 
 if (mParent == null) { 
  //這里會(huì)啟動(dòng)新的Activity,核心功能都在mMainThread.getApplicationThread()中完成 
  Instrumentation.ActivityResult ar = 
   mInstrumentation.execStartActivity( 
    this, mMainThread.getApplicationThread(), mToken, this, 
    intent, requestCode, options); 
  if (ar != null) { 
   //發(fā)送結(jié)果,即onActivityResult會(huì)被調(diào)用 
   mMainThread.sendActivityResult( 
    mToken, mEmbeddedID, requestCode, ar.getResultCode(), 
    ar.getResultData()); 
  } 
  if (requestCode >= 0) { 
   // If this start is requesting a result, we can avoid making 
   // the activity visible until the result is received. Setting 
   // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the 
   // activity hidden during this time, to avoid flickering. 
   // This can only be done when a result is requested because 
   // that guarantees we will get information back when the 
   // activity is finished, no matter what happens to it. 
   mStartedActivity = true; 
  } 
 
  final View decor = mWindow != null ? mWindow.peekDecorView() : null; 
  if (decor != null) { 
   decor.cancelPendingInputEvents(); 
  } 
  // TODO Consider clearing/flushing other event sources and events for child windows. 
 } else { 
  //在ActivityGroup內(nèi)部的Activity調(diào)用startActivity的時(shí)候會(huì)走到這里,內(nèi)部處理邏輯和上面是類似的 
  if (options != null) { 
   mParent.startActivityFromChild(this, intent, requestCode, options); 
  } else { 
   // Note we want to go through this method for compatibility with 
   // existing applications that may have overridden it. 
   mParent.startActivityFromChild(this, intent, requestCode); 
  } 
 } 
} 

說(shuō)明:上述代碼關(guān)鍵點(diǎn)都有注釋了,可以發(fā)現(xiàn),真正打開(kāi)activity的實(shí)現(xiàn)在Instrumentation的execStartActivity方法中,去看看

code:Instrumentation#execStartActivity

public ActivityResult execStartActivity( 
  Context who, IBinder contextThread, IBinder token, Activity target, 
  Intent intent, int requestCode, Bundle options) { 
 //核心功能在這個(gè)whoThread中完成,其內(nèi)部scheduleLaunchActivity方法用于完成activity的打開(kāi) 
 IApplicationThread whoThread = (IApplicationThread) contextThread; 
 if (mActivityMonitors != null) { 
  synchronized (mSync) { 
   //先查找一遍看是否存在這個(gè)activity 
   final int N = mActivityMonitors.size(); 
   for (int i=0; i<N; i++) { 
    final ActivityMonitor am = mActivityMonitors.get(i); 
    if (am.match(who, null, intent)) { 
     //如果找到了就跳出循環(huán) 
     am.mHits++; 
     //如果目標(biāo)activity無(wú)法打開(kāi),直接return 
     if (am.isBlocking()) { 
      return requestCode >= 0 ? am.getResult() : null; 
     } 
     break; 
    } 
   } 
  } 
 } 
 try { 
  intent.migrateExtraStreamToClipData(); 
  intent.prepareToLeaveProcess(); 
  //這里才是真正打開(kāi)activity的地方,核心功能在whoThread中完成。 
  int result = ActivityManagerNative.getDefault() 
   .startActivity(whoThread, who.getBasePackageName(), intent, 
     intent.resolveTypeIfNeeded(who.getContentResolver()), 
     token, target != null ? target.mEmbeddedID : null, 
     requestCode, 0, null, null, options); 
  //這個(gè)方法是專門拋異常的,它會(huì)對(duì)結(jié)果進(jìn)行檢查,如果無(wú)法打開(kāi)activity, 
  //則拋出諸如ActivityNotFoundException類似的各種異常 
  checkStartActivityResult(result, intent); 
 } catch (RemoteException e) { 
 } 
 return null; 
} 

說(shuō)明:我想再說(shuō)一下這個(gè)方法checkStartActivityResult,它也專業(yè)拋異常的,看代碼,相信大家對(duì)下面的異常信息不陌生吧,就是它干的,其中最熟悉的非Unable to find explicit activity class莫屬了,如果你在xml中沒(méi)有注冊(cè)目標(biāo)activity,此異常將會(huì)拋出。

/*package*/ static void checkStartActivityResult(int res, Object intent) { 
 if (res >= ActivityManager.START_SUCCESS) { 
  return; 
 } 
  
 switch (res) { 
  case ActivityManager.START_INTENT_NOT_RESOLVED: 
  case ActivityManager.START_CLASS_NOT_FOUND: 
   if (intent instanceof Intent && ((Intent)intent).getComponent() != null) 
    throw new ActivityNotFoundException( 
      "Unable to find explicit activity class " 
      + ((Intent)intent).getComponent().toShortString() 
      + "; have you declared this activity in your AndroidManifest.xml?"); 
   throw new ActivityNotFoundException( 
     "No Activity found to handle " + intent); 
  case ActivityManager.START_PERMISSION_DENIED: 
   throw new SecurityException("Not allowed to start activity " 
     + intent); 
  case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: 
   throw new AndroidRuntimeException( 
     "FORWARD_RESULT_FLAG used while also requesting a result"); 
  case ActivityManager.START_NOT_ACTIVITY: 
   throw new IllegalArgumentException( 
     "PendingIntent is not an activity"); 
  default: 
   throw new AndroidRuntimeException("Unknown error code " 
     + res + " when starting " + intent); 
 } 
} 

接下來(lái)我們要去看看IApplicationThread,因?yàn)楹诵墓δ苡善鋬?nèi)部的scheduleLaunchActivity方法來(lái)完成,由于IApplicationThread是個(gè)接口,所以,我們需要找到它的實(shí)現(xiàn)類,我已經(jīng)幫大家找到了,它就是ActivityThread中的內(nèi)部類ApplicationThread,看下它的繼承關(guān)系:

private class ApplicationThread extends ApplicationThreadNative;

public abstract class ApplicationThreadNative extends Binder implements IApplicationThread;

可以發(fā)現(xiàn),ApplicationThread還是間接實(shí)現(xiàn)了IApplicationThread接口,先看下這個(gè)類的結(jié)構(gòu)

看完ApplicationThread的大致結(jié)構(gòu),我們應(yīng)該能夠猜測(cè)到,Activity的生命周期中的resume、newIntent、pause、stop等事件都是由它觸發(fā)的,事實(shí)上,的確是這樣的。這里,我們?yōu)榱苏f(shuō)明問(wèn)題,僅僅看scheduleLaunchActivity方法

code:ApplicationThread#scheduleLaunchActivity

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, 
  ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, 
  int procState, Bundle state, List<ResultInfo> pendingResults, 
  List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, 
  String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { 
 
 updateProcessState(procState, false); 
 
 ActivityClientRecord r = new ActivityClientRecord(); 
 
 r.token = token; 
 r.ident = ident; 
 r.intent = intent; 
 r.activityInfo = info; 
 r.compatInfo = compatInfo; 
 r.state = state; 
 
 r.pendingResults = pendingResults; 
 r.pendingIntents = pendingNewIntents; 
 
 r.startsNotResumed = notResumed; 
 r.isForward = isForward; 
 
 r.profileFile = profileName; 
 r.profileFd = profileFd; 
 r.autoStopProfiler = autoStopProfiler; 
 
 updatePendingConfiguration(curConfig); 
 
 queueOrSendMessage(H.LAUNCH_ACTIVITY, r); 
} 

說(shuō)明:上述代碼很好理解,構(gòu)造一個(gè)activity記錄,然后發(fā)送一個(gè)消息,所以,我們要看看Handler是如何處理這個(gè)消息的,現(xiàn)在轉(zhuǎn)到這個(gè)Handler,它有個(gè)很短的名字叫做H

code:ActivityThread#H

//這個(gè)類太長(zhǎng),我只帖出了我們用到的部分 
private class H extends Handler { 
 
 public void handleMessage(Message msg) { 
  if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); 
  switch (msg.what) { 
   //這里處理LAUNCH_ACTIVITY消息類型 
   case LAUNCH_ACTIVITY: { 
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); 
    ActivityClientRecord r = (ActivityClientRecord)msg.obj; 
 
    r.packageInfo = getPackageInfoNoCheck( 
      r.activityInfo.applicationInfo, r.compatInfo); 
    //這里處理startActivity消息 
    handleLaunchActivity(r, null); 
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 
   } break; 
   case RELAUNCH_ACTIVITY: { 
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); 
    ActivityClientRecord r = (ActivityClientRecord)msg.obj; 
    handleRelaunchActivity(r); 
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 
   } break; 
   case PAUSE_ACTIVITY: 
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); 
    handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2); 
    maybeSnapshot(); 
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 
    break; 
   ... 
  } 
} 

說(shuō)明:看來(lái)還要看handleLaunchActivity

code:ActivityThread#handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
 // If we are getting ready to gc after going to the background, well 
 // we are back active so skip it. 
 unscheduleGcIdler(); 
 
 if (r.profileFd != null) { 
  mProfiler.setProfiler(r.profileFile, r.profileFd); 
  mProfiler.startProfiling(); 
  mProfiler.autoStopProfiler = r.autoStopProfiler; 
 } 
 
 // Make sure we are running with the most recent config. 
 handleConfigurationChanged(null, null); 
 
 if (localLOGV) Slog.v( 
  TAG, "Handling launch of " + r); 
 //終于到底了,大家都有點(diǎn)不耐煩了吧,從方法名可以看出, 
 //performLaunchActivity真正完成了activity的調(diào)起, 
 //同時(shí)activity會(huì)被實(shí)例化,并且onCreate會(huì)被調(diào)用 
 Activity a = performLaunchActivity(r, customIntent); 
 
 if (a != null) { 
  r.createdConfig = new Configuration(mConfiguration); 
  Bundle oldState = r.state; 
  //看到?jīng)],目標(biāo)activity的onResume會(huì)被調(diào)用 
  handleResumeActivity(r.token, false, r.isForward, 
    !r.activity.mFinished && !r.startsNotResumed); 
 
  if (!r.activity.mFinished && r.startsNotResumed) { 
   // The activity manager actually wants this one to start out 
   // paused, because it needs to be visible but isn't in the 
   // foreground. We accomplish this by going through the 
   // normal startup (because activities expect to go through 
   // onResume() the first time they run, before their window 
   // is displayed), and then pausing it. However, in this case 
   // we do -not- need to do the full pause cycle (of freezing 
   // and such) because the activity manager assumes it can just 
   // retain the current state it has. 
   try { 
    r.activity.mCalled = false; 
    //同時(shí),由于新activity被調(diào)起了,原activity的onPause會(huì)被調(diào)用 
    mInstrumentation.callActivityOnPause(r.activity); 
    // We need to keep around the original state, in case 
    // we need to be created again. But we only do this 
    // for pre-Honeycomb apps, which always save their state 
    // when pausing, so we can not have them save their state 
    // when restarting from a paused state. For HC and later, 
    // we want to (and can) let the state be saved as the normal 
    // part of stopping the activity. 
    if (r.isPreHoneycomb()) { 
     r.state = oldState; 
    } 
    if (!r.activity.mCalled) { 
     throw new SuperNotCalledException( 
      "Activity " + r.intent.getComponent().toShortString() + 
      " did not call through to super.onPause()"); 
    } 
 
   } catch (SuperNotCalledException e) { 
    throw e; 
 
   } catch (Exception e) { 
    if (!mInstrumentation.onException(r.activity, e)) { 
     throw new RuntimeException( 
       "Unable to pause activity " 
       + r.intent.getComponent().toShortString() 
       + ": " + e.toString(), e); 
    } 
   } 
   r.paused = true; 
  } 
 } else { 
  // If there was an error, for any reason, tell the activity 
  // manager to stop us. 
  try { 
   ActivityManagerNative.getDefault() 
    .finishActivity(r.token, Activity.RESULT_CANCELED, null); 
  } catch (RemoteException ex) { 
   // Ignore 
  } 
 } 
} 

說(shuō)明:關(guān)于原activity和新activity之間的狀態(tài)同步,如果大家感興趣可以自己研究下,因?yàn)檫壿嬏珡?fù)雜,我沒(méi)法把所有問(wèn)題都說(shuō)清楚,否則就太深入細(xì)節(jié)而淹沒(méi)了整體邏輯,研究源碼要的就是清楚整體邏輯。下面看最后一個(gè)方法,這個(gè)方法是activity的啟動(dòng)過(guò)程的真正實(shí)現(xiàn)。

code:ActivityThread#performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
 // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); 
 
 ActivityInfo aInfo = r.activityInfo; 
 if (r.packageInfo == null) { 
  r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, 
    Context.CONTEXT_INCLUDE_CODE); 
 } 
 //首先從intent中解析出目標(biāo)activity的啟動(dòng)參數(shù) 
 ComponentName component = r.intent.getComponent(); 
 if (component == null) { 
  component = r.intent.resolveActivity( 
   mInitialApplication.getPackageManager()); 
  r.intent.setComponent(component); 
 } 
 
 if (r.activityInfo.targetActivity != null) { 
  component = new ComponentName(r.activityInfo.packageName, 
    r.activityInfo.targetActivity); 
 } 
 
 Activity activity = null; 
 try { 
  java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); 
  //用ClassLoader(類加載器)將目標(biāo)activity的類通過(guò)類名加載進(jìn)來(lái)并調(diào)用newInstance來(lái)實(shí)例化一個(gè)對(duì)象 
  //其實(shí)就是通過(guò)Activity的無(wú)參構(gòu)造方法來(lái)new一個(gè)對(duì)象,對(duì)象就是在這里new出來(lái)的。 
  activity = mInstrumentation.newActivity( 
    cl, component.getClassName(), r.intent); 
  StrictMode.incrementExpectedActivityCount(activity.getClass()); 
  r.intent.setExtrasClassLoader(cl); 
  if (r.state != null) { 
   r.state.setClassLoader(cl); 
  } 
 } catch (Exception e) { 
  if (!mInstrumentation.onException(activity, e)) { 
   throw new RuntimeException( 
    "Unable to instantiate activity " + component 
    + ": " + e.toString(), e); 
  } 
 } 
 
 try { 
  Application app = r.packageInfo.makeApplication(false, mInstrumentation); 
 
  if (localLOGV) Slog.v(TAG, "Performing launch of " + r); 
  if (localLOGV) Slog.v( 
    TAG, r + ": app=" + app 
    + ", appName=" + app.getPackageName() 
    + ", pkg=" + r.packageInfo.getPackageName() 
    + ", comp=" + r.intent.getComponent().toShortString() 
    + ", dir=" + r.packageInfo.getAppDir()); 
 
  if (activity != null) { 
   Context appContext = createBaseContextForActivity(r, activity); 
   CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); 
   Configuration config = new Configuration(mCompatConfiguration); 
   if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " 
     + r.activityInfo.name + " with config " + config); 
   activity.attach(appContext, this, getInstrumentation(), r.token, 
     r.ident, app, r.intent, r.activityInfo, title, r.parent, 
     r.embeddedID, r.lastNonConfigurationInstances, config); 
 
   if (customIntent != null) { 
    activity.mIntent = customIntent; 
   } 
   r.lastNonConfigurationInstances = null; 
   activity.mStartedActivity = false; 
   int theme = r.activityInfo.getThemeResource() 
   if (theme != 0) { 
    activity.setTheme(theme); 
   } 
 
   activity.mCalled = false; 
   //目標(biāo)activity的onCreate被調(diào)用了,到此為止,Activity被啟動(dòng)了,接下來(lái)的流程就是Activity的生命周期了, 
   //本文之前已經(jīng)提到,其生命周期的各種狀態(tài)的切換由ApplicationThread內(nèi)部來(lái)完成 
   mInstrumentation.callActivityOnCreate(activity, r.state); 
   if (!activity.mCalled) { 
    throw new SuperNotCalledException( 
     "Activity " + r.intent.getComponent().toShortString() + 
     " did not call through to super.onCreate()"); 
   } 
   r.activity = activity; 
   r.stopped = true; 
   if (!r.activity.mFinished) { 
    activity.performStart(); 
    r.stopped = false; 
   } 
   if (!r.activity.mFinished) { 
    if (r.state != null) { 
     mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); 
    } 
   } 
   if (!r.activity.mFinished) { 
    activity.mCalled = false; 
    mInstrumentation.callActivityOnPostCreate(activity, r.state); 
    if (!activity.mCalled) { 
     throw new SuperNotCalledException( 
      "Activity " + r.intent.getComponent().toShortString() + 
      " did not call through to super.onPostCreate()"); 
    } 
   } 
  } 
  r.paused = true; 
 
  mActivities.put(r.token, r); 
 
 } catch (SuperNotCalledException e) { 
  throw e; 
 
 } catch (Exception e) { 
  if (!mInstrumentation.onException(activity, e)) { 
   throw new RuntimeException( 
    "Unable to start activity " + component 
    + ": " + e.toString(), e); 
  } 
 } 
 
 return activity; 
} 

總結(jié)

相信當(dāng)你看到這里的時(shí)候,你對(duì)Activity的啟動(dòng)過(guò)程應(yīng)該有了一個(gè)感性的認(rèn)識(shí)。Activity很復(fù)雜,特性很多,本文沒(méi)法對(duì)各個(gè)細(xì)節(jié)進(jìn)行深入分析,而且就算真的對(duì)各個(gè)細(xì)節(jié)都進(jìn)行了深入分析,那文章要有多長(zhǎng)啊,還有人有耐心看下去嗎?希望本文能夠給大家?guī)?lái)一些幫助,謝謝大家閱讀。 也希望大家多多支持腳本之家。

相關(guān)文章

  • android 添加按(power鍵)電源鍵結(jié)束通話(掛斷電話)

    android 添加按(power鍵)電源鍵結(jié)束通話(掛斷電話)

    首先我們發(fā)現(xiàn)現(xiàn)在我們所用的android智能手機(jī)大部分都有當(dāng)你在打電話時(shí)按power鍵來(lái)掛斷電話,一般都是在設(shè)置中
    2013-01-01
  • 詳解Android 視頻播放時(shí)停止后臺(tái)運(yùn)行的方法

    詳解Android 視頻播放時(shí)停止后臺(tái)運(yùn)行的方法

    這篇文章主要介紹了詳解Android 視頻播放時(shí)停止后臺(tái)運(yùn)行的方法的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • Android?文件存儲(chǔ)系統(tǒng)原理

    Android?文件存儲(chǔ)系統(tǒng)原理

    這篇文章主要介紹了Android?文件存儲(chǔ)系統(tǒng)原理,Android?的文件系統(tǒng)類似于其他平臺(tái)的基于磁盤的文件系統(tǒng),包括好幾種類別具體詳情感興趣得朋友可以參考一下文章內(nèi)容
    2022-06-06
  • 使用kotlin協(xié)程提高app性能(譯)

    使用kotlin協(xié)程提高app性能(譯)

    這篇文章主要介紹了使用kotlin協(xié)程提高app性能(譯),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • Android 自定義Livedata使用示例解析

    Android 自定義Livedata使用示例解析

    這篇文章主要為大家介紹了Android 自定義Livedata使用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Android10.0實(shí)現(xiàn)本地音樂(lè)播放(附源碼下載)

    Android10.0實(shí)現(xiàn)本地音樂(lè)播放(附源碼下載)

    這篇文章主要介紹了Android10.0實(shí)現(xiàn)本地音樂(lè)播放(附源碼下載),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Android仿微信微博多圖展示效果

    Android仿微信微博多圖展示效果

    這篇文章主要為大家詳細(xì)介紹了Android仿微信微博多圖展示效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-02-02
  • Android進(jìn)階之使用時(shí)間戳計(jì)算時(shí)間差

    Android進(jìn)階之使用時(shí)間戳計(jì)算時(shí)間差

    這篇文章主要為大家詳細(xì)介紹了Android進(jìn)階之使用時(shí)間戳計(jì)算時(shí)間差,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android利用CircleImageView實(shí)現(xiàn)圓形頭像的方法

    Android利用CircleImageView實(shí)現(xiàn)圓形頭像的方法

    這篇文章主要介紹了Android利用CircleImageView實(shí)現(xiàn)圓形頭像的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Android自定義控件實(shí)現(xiàn)icon+文字的多種效果

    Android自定義控件實(shí)現(xiàn)icon+文字的多種效果

    這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)icon+文字的多種效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01

最新評(píng)論