本文 基于Android8.1系统源码分析
本文以TextView为例描述一下,点击事件的分发流程
如下图:
去除下图所示提示框:
修改文件:frameworks/base/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
增加import头
1 2 3 4 5 |
import android.os.IBinder; import android.os.ServiceManager; import android.util.Log; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbManager; |
在处理MESSAGE_ADB_CONFIRM消息那一段,把startConfirmation(key, mFingerprints);去掉,改成如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
case MESSAGE_ADB_CONFIRM: { if ("trigger_restart_min_framework".equals( SystemProperties.get("vold.decrypt"))) { Slog.d(TAG, "Deferring adb confirmation until after vold decrypt"); if (mThread != null) { mThread.sendResponse("NO"); } break; } String key = (String)msg.obj; String fingerprints = getFingerprints(key); if ("".equals(fingerprints)) { if (mThread != null) { mThread.sendResponse("NO"); } break; } try { IBinder b = ServiceManager.getService(Context.USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); service.allowUsbDebugging(true, key); } catch (Exception e) { Log.e(TAG, "Unable to notify Usb service", e); } mFingerprints = fingerprints; //去掉startConfirmation函数 //startConfirmation(key, mFingerprints); break; } |
首先是在ViewRootImpl.java中的performTraversals中开始的?
最主要的有三个子函数
performMeasure、performLayout、performDraw
这三个函数分别对应View中的onMeasure、onLayout、onDraw
最后使用setMeasuredDimension设置view的宽度和高度
Choreographer是用于协调动画、输入和绘图的时间的。
Choreographer从显示子系统接收时间脉冲(比如垂直同步),然后安排下一个显示帧渲染工作。
应用通常通过动画框架和视图层次结构的高级抽象方法与choreographer进行交互。下面是一些可以通过高级api可以做的操作:
使用android.animation.ValueAnimator#start
未完……
1. CALLBACK_INPUT = 0
输入回调,最先执行的,优先级比较高
2. CALLBACK_ANIMATION = 1
动画回调,在traversals之前运行
3. CALLBACK_TRAVERSAL = 2;
遍历回调,处理layout和draw,在所有的异步信息处理之后运行。
4. CALLBACK_COMMIT = 3;
处理frame的post-draw操作。在traversal操作完成后运行。
是由一个CallbackRecord组成的链表来记录要执行的各种回调的
是由一个CallbackQueue数组来管理这些链表的(添加、删除),每一种回调类型一个CallbackQueue
mHead是第一个CallbackRecord,执行时间最近的一个
为什么要重新写一个CallbackQueue?
因为这个队列使用的很频繁,所以自己封装一个轻量级的Queue
如上图:上层框架创建Choreographer的实例,并循环进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) { CallbackRecord callback = mCallbackPool; if (callback == null) { callback = new CallbackRecord(); } else { mCallbackPool = callback.next; callback.next = null; } callback.dueTime = dueTime; callback.action = action; callback.token = token; return callback; } private void recycleCallbackLocked(CallbackRecord callback) { callback.action = null; callback.token = null; callback.next = mCallbackPool; mCallbackPool = callback; } |
其中obtainCallbackLocked是用于试图新建一个CallbackRecord,recycleCallbackLocked是用于回收一个CallbackRecord
重复利用mCallbackPool,尽量避免重复创建CallbackRecord
因为它还有自己其他的业务逻辑,比如CallbackQueue。
见AppWindowContainerController.java中的addStartingWindow函数
刚启动程序时,会先弹出一个Activity,那个其实不是主Activity,是framework层创建的一个临时Activity
在开发手机应用时startingWindow是不能去掉的,所以现在一般采取的方案是启动透明窗体或者先启动一个图片闪屏界面
在开发定制系统时,可以去掉,但是点击按钮后过几百毫秒后才会显示Activity,去掉的方法:
在AppWindowContainerController.java中的addStartingWindow函数中提前return false
见ResourceImpl.java中的updateConfiguration函数
如果你的应用只有中文,可以在AndroidManifest.xml中的Activity的节点中增加android:configChanges=”locale”以优化?
3. Choreographer.java中的doFrame函数
本文基于Android8.1系统
应用执行过程:在launcher中打开一个无任何组件的应用
updateConfiguration函数增加计时后的代码(使用SystemClock.elapsedRealtime()来计算时间,单位:毫秒)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
public void updateConfiguration(Configuration config, DisplayMetrics metrics, CompatibilityInfo compat) { new Exception().printStackTrace(); Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration"); try { long starttime = SystemClock.elapsedRealtime(); synchronized (mAccessLock) { if (false) { Slog.i(TAG, "**** Updating config of " + this + ": old config is " + mConfiguration + " old compat is " + mDisplayAdjustments.getCompatibilityInfo()); Slog.i(TAG, "**** Updating config of " + this + ": new config is " + config + " new compat is " + compat); } if (compat != null) { mDisplayAdjustments.setCompatibilityInfo(compat); } if (metrics != null) { mMetrics.setTo(metrics); } Slog.d(TAG, "time1 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; // NOTE: We should re-arrange this code to create a Display // with the CompatibilityInfo that is used everywhere we deal // with the display in relation to this app, rather than // doing the conversion here. This impl should be okay because // we make sure to return a compatible display in the places // where there are public APIs to retrieve the display... but // it would be cleaner and more maintainable to just be // consistently dealing with a compatible display everywhere in // the framework. mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics); Slog.d(TAG, "time2 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; final @Config int configChanges = calcConfigChanges(config); Slog.d(TAG, "time3 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; // If even after the update there are no Locales set, grab the default locales. LocaleList locales = mConfiguration.getLocales(); if (locales.isEmpty()) { locales = LocaleList.getDefault(); mConfiguration.setLocales(locales); } Slog.d(TAG, "time4 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) { if (locales.size() > 1) { // The LocaleList has changed. We must query the AssetManager's available // Locales and figure out the best matching Locale in the new LocaleList. String[] availableLocales = mAssets.getNonSystemLocales(); Slog.d(TAG, "time5_1 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; if (LocaleList.isPseudoLocalesOnly(availableLocales)) { Slog.d(TAG, "time5_2 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; // No app defined locales, so grab the system locales. availableLocales = mAssets.getLocales(); Slog.d(TAG, "time5_3 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; if (LocaleList.isPseudoLocalesOnly(availableLocales)) { availableLocales = null; } Slog.d(TAG, "time5_4 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; } if (availableLocales != null) { final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( availableLocales); Slog.d(TAG, "time5_5 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; if (bestLocale != null && bestLocale != locales.get(0)) { mConfiguration.setLocales(new LocaleList(bestLocale, locales)); Slog.d(TAG, "time5_6 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; } } } } Slog.d(TAG, "time5 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { mMetrics.densityDpi = mConfiguration.densityDpi; mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; } Slog.d(TAG, "time6 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; // Protect against an unset fontScale. mMetrics.scaledDensity = mMetrics.density * (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f); final int width, height; if (mMetrics.widthPixels >= mMetrics.heightPixels) { width = mMetrics.widthPixels; height = mMetrics.heightPixels; } else { //noinspection SuspiciousNameCombination width = mMetrics.heightPixels; //noinspection SuspiciousNameCombination height = mMetrics.widthPixels; } final int keyboardHidden; if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; } else { keyboardHidden = mConfiguration.keyboardHidden; } mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()), mConfiguration.orientation, mConfiguration.touchscreen, mConfiguration.densityDpi, mConfiguration.keyboard, keyboardHidden, mConfiguration.navigation, width, height, mConfiguration.smallestScreenWidthDp, mConfiguration.screenWidthDp, mConfiguration.screenHeightDp, mConfiguration.screenLayout, mConfiguration.uiMode, mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT); Slog.d(TAG, "time7 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; if (DEBUG_CONFIG) { Slog.i(TAG, "**** Updating config of " + this + ": final config is " + mConfiguration + " final compat is " + mDisplayAdjustments.getCompatibilityInfo()); } mDrawableCache.onConfigurationChange(configChanges); Slog.d(TAG, "time8 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; Slog.d(TAG, "time9 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; mColorDrawableCache.onConfigurationChange(configChanges); Slog.d(TAG, "time10 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; mComplexColorCache.onConfigurationChange(configChanges); Slog.d(TAG, "time11 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; mAnimatorCache.onConfigurationChange(configChanges); Slog.d(TAG, "time12 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; mStateListAnimatorCache.onConfigurationChange(configChanges); Slog.d(TAG, "time13 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; flushLayoutCache(); Slog.d(TAG, "time14 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; } synchronized (sSync) { if (mPluralRule != null) { mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0)); } } Slog.d(TAG, "time15 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime();; } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } } |
会间接调用到LocaleList.java中的computeFirstMatchIndex函数
此函数加时间计数后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
private int computeFirstMatchIndex(Collection<String> supportedLocales, boolean assumeEnglishIsSupported) { if (mList.length == 1) { // just one locale, perhaps the most common scenario return 0; } if (mList.length == 0) { // empty locale list return -1; } long starttime = SystemClock.elapsedRealtime(); int bestIndex = Integer.MAX_VALUE; // Try English first, so we can return early if it's in the LocaleList if (assumeEnglishIsSupported) { final int idx = findFirstMatchIndex(EN_LATN); Slog.d(TAG, "computeFirstMatchIndex1 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime(); if (idx == 0) { // We have a match on the first locale, which is good enough return 0; } else if (idx < bestIndex) { bestIndex = idx; } } for (String languageTag : supportedLocales) { final Locale supportedLocale = Locale.forLanguageTag(languageTag); Slog.d(TAG, "computeFirstMatchIndex2 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime(); // We expect the average length of locale lists used for locale resolution to be // smaller than three, so it's OK to do this as an O(mn) algorithm. final int idx = findFirstMatchIndex(supportedLocale); Slog.d(TAG, "computeFirstMatchIndex3 : " + (SystemClock.elapsedRealtime() - starttime) + ", by liuderu"); starttime = SystemClock.elapsedRealtime(); if (idx == 0) { // We have a match on the first locale, which is good enough return 0; } else if (idx < bestIndex) { bestIndex = idx; } } if (bestIndex == Integer.MAX_VALUE) { // no match was found, so we fall back to the first locale in the locale list return 0; } else { return bestIndex; } } |
执行后的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
05-19 05:20:19.008 3769 3769 D Resources: time1 : 2, by liuderu 05-19 05:20:19.014 3769 3769 D Resources: time2 : 1, by liuderu 05-19 05:20:19.015 3769 3769 D Resources: time3 : 1, by liuderu 05-19 05:20:19.024 3769 3769 D Resources: time4 : 8, by liuderu 05-19 05:20:19.024 3769 3769 D Resources: time5_1 : 0, by liuderu 05-19 05:20:19.027 3769 3769 D Resources: time5_2 : 0, by liuderu 05-19 05:20:19.042 3769 3769 D Resources: time5_3 : 8, by liuderu 05-19 05:20:19.043 3769 3769 D Resources: time5_4 : 1, by liuderu 05-19 05:20:19.211 3769 3769 D LocaleList: computeFirstMatchIndex1 : 167, by liuderu 05-19 05:20:19.229 3769 3769 D LocaleList: computeFirstMatchIndex2 : 15, by liuderu 05-19 05:20:19.235 3769 3769 D LocaleList: computeFirstMatchIndex3 : 5, by liuderu 05-19 05:20:19.253 3769 3769 D LocaleList: computeFirstMatchIndex2 : 18, by liuderu 05-19 05:20:19.265 3769 3769 D LocaleList: computeFirstMatchIndex3 : 10, by liuderu 05-19 05:20:19.271 3769 3769 D LocaleList: computeFirstMatchIndex2 : 6, by liuderu 05-19 05:20:19.378 3769 3769 D LocaleList: computeFirstMatchIndex3 : 98, by liuderu 05-19 05:20:19.397 3769 3769 D LocaleList: computeFirstMatchIndex2 : 11, by liuderu 05-19 05:20:19.433 3769 3769 D LocaleList: computeFirstMatchIndex3 : 36, by liuderu 05-19 05:20:19.435 3769 3769 D LocaleList: computeFirstMatchIndex2 : 1, by liuderu 05-19 05:20:19.450 3769 3769 D LocaleList: computeFirstMatchIndex3 : 15, by liuderu 05-19 05:20:19.451 3769 3769 D LocaleList: computeFirstMatchIndex2 : 1, by liuderu 05-19 05:20:19.463 3769 3769 D LocaleList: computeFirstMatchIndex3 : 12, by liuderu 05-19 05:20:19.465 3769 3769 D LocaleList: computeFirstMatchIndex2 : 2, by liuderu 05-19 05:20:19.477 3769 3769 D LocaleList: computeFirstMatchIndex3 : 11, by liuderu 05-19 05:20:19.478 3769 3769 D LocaleList: computeFirstMatchIndex2 : 1, by liuderu 05-19 05:20:19.478 3769 3769 D LocaleList: computeFirstMatchIndex3 : 0, by liuderu 05-19 05:20:19.480 3769 3769 D LocaleList: computeFirstMatchIndex2 : 1, by liuderu 05-19 05:20:19.491 3769 3769 D LocaleList: computeFirstMatchIndex3 : 11, by liuderu 05-19 05:20:19.492 3769 3769 D LocaleList: computeFirstMatchIndex2 : 1, by liuderu 05-19 05:20:19.493 3769 3769 D LocaleList: computeFirstMatchIndex3 : 0, by liuderu 05-19 05:20:19.494 3769 3769 D LocaleList: computeFirstMatchIndex2 : 1, by liuderu 05-19 05:20:19.501 3769 3769 D LocaleList: computeFirstMatchIndex3 : 6, by liuderu 05-19 05:20:19.501 3769 3769 D Resources: time5_5 : 458, by liuderu 05-19 05:20:19.502 3769 3769 D Resources: time5 : 0, by liuderu 05-19 05:20:19.502 3769 3769 D Resources: time6 : 0, by liuderu 05-19 05:20:19.503 3769 3769 D Resources: time7 : 1, by liuderu 05-19 05:20:19.503 3769 3769 D Resources: time8 : 0, by liuderu 05-19 05:20:19.503 3769 3769 D Resources: time9 : 0, by liuderu 05-19 05:20:19.504 3769 3769 D Resources: time10 : 0, by liuderu 05-19 05:20:19.504 3769 3769 D Resources: time11 : 0, by liuderu 05-19 05:20:19.504 3769 3769 D Resources: time12 : 0, by liuderu 05-19 05:20:19.504 3769 3769 D Resources: time13 : 0, by liuderu 05-19 05:20:19.504 3769 3769 D Resources: time14 : 0, by liuderu 05-19 05:20:19.504 3769 3769 D Resources: time15 : 0, by liuderu |
由此可见time5_5耗时较长,time5_5的耗时是因为computeFirstMatchIndex中的循环造成的。
而且在一个应用启动过程中要执行两次updateConfiguration,所以这个值得研究一下。
1 |
atrace -a com.jeoe.ebox gfx wm am sched view app res ss |
这样会把结果输出到控制台,对分析没什么意义,可以用于测试这个命令
1 |
atrace -a com.jeoe.ebox gfx wm am sched view app res ss -o /sdcard/a.trace |
这样会把分析文件输出到/sdcard/a.trace文件
将a.trace文件导出到电脑,然后使用sdk下的systrace.py程序将分析结果生成html文件,命令如下:
1 |
python systrace.py --from-file ~/a.trace -o ~/a.htm |
–from-file 参数表明从文件读入分析数据
~/a.trace 源数据文件
-o ~/a.html 指定输出文件路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
usage: atrace [options] [categories...] options include: -a appname enable app-level tracing for a comma separated list of cmdlines -b N use a trace buffer size of N KB -c trace into a circular buffer -f filename use the categories written in a file as space-separated values in a line -k fname,... trace the listed kernel functions -n ignore signals -s N sleep for N seconds before tracing [default 0] -t N trace for N seconds [default 5] -z compress the trace dump --async_start start circular trace and return immediately --async_dump dump the current contents of circular trace buffer --async_stop stop tracing and dump the current contents of circular trace buffer --stream stream trace to stdout as it enters the trace buffer Note: this can take significant CPU time, and is best used for measuring things that are not affected by CPU performance, like pagecache usage. --list_categories list the available tracing categories -o filename write the trace to the specified file instead of stdout. |
执行下面命令可以看到atrace允许追踪的category
1 |
atrace --list_categories |
下面是笔者附上来的列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
gfx - Graphics input - Input view - View System webview - WebView wm - Window Manager am - Activity Manager sm - Sync Manager audio - Audio video - Video camera - Camera hal - Hardware Modules app - Application res - Resource Loading dalvik - Dalvik VM rs - RenderScript bionic - Bionic C Library power - Power Management pm - Package Manager ss - System Server database - Database network - Network adb - ADB pdx - PDX services sched - CPU Scheduling irq - IRQ Events i2c - I2C Events freq - CPU Frequency idle - CPU Idle disk - Disk I/O mmc - eMMC commands workq - Kernel Workqueues regulators - Voltage and Current Regulators binder_driver - Binder Kernel driver binder_lock - Binder global lock trace pagecache - Page cache |
系统版本大于等于Android 4.3 (API18)以上的才可以用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import android.content.Intent import android.os.Build import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Trace class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { Trace.beginSection("aaabbbcccddd") }; //*************您的代码 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { Trace.endSection(); }; } } |
执行python脚本时提示ImportError: No module named six
可能机器上没有python的six模块
(本文基于Ubuntu 18.04.4)
执行以下命令安装
1 |
pip install six |
如果提示你电脑上没有pip命令,则先安装python-pip
1 |
sudo apt install python-pip |
今天看到这样一个面试题,遂看了下Android系统源码
发现在ActivityThread.java中的performLaunchActivity函数中有这么一段代码
1 2 3 4 |
if (!r.activity.mFinished) { activity.performStart(); r.stopped = false; } |
可见,如果在onStart之前如果Activity执行了finish函数,就不会再执行onStart函数了。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import androidx.appcompat.app.AppCompatActivity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; import android.view.View; import static java.lang.ClassLoader.getSystemClassLoader; public class MainActivity extends AppCompatActivity { private String LOG_TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, "onCreate"); finish(); } @Override protected void onStart() { Log.d(LOG_TAG, "onStart"); super.onStart(); } } |
本文基于Android8.1系统
因为安卓系统默认测试网络的连接是用的google相关的域名,在国内网络无法访问,所以造成即手机连上网络也会出现“已连接,但无法访问互联网”的问题。
可以使用下面两个方法其中之一修改,换成国内可访问的链接
1. 修改文件frameworks/base/services/core/java/com/android/server/connectivity/NetworkMonitor.java
将文件中常量DEFAULT_HTTPS_URL和DEFAULT_HTTP_URL对应的URL换成可访问的(注意:链接是需要返回http 204返回码的)
2. 修改Android的settings库中Global域中captive_portal_http_url和captive_portal_https_url的值
笔者用的第一种方法,第二种方法没做测试,修改默认设置值请参考下面的链接
http://clients1.google.com/generate_204
http:// clients3.google.com/generate_204
http://www.google-analytics.com/generate_204
http://httpbin.coding.io/status/204
http://www.google.cn/generate_204
http://gen204.sinaapp.com/generate_204
http://g.cn/generate_204
https://mine260309.me/generate_204
http://ping.mine260309.me/generate_204
http://noisyfox.cn/generate_204
https://204.io/generate_204
https://mine260309.me/archives/1587