Launcher3 安装App加载显示过程分析,androidstudio开发app实例
“Initializing LauncherAppState in the absence of LauncherProvider”);
}
Log.v(Launcher.TAG, “LauncherAppState initiated”);
Preconditions.assertUIThread();
mContext = context;
mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
//初始化接口
LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);
/**
- Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
public void onTerminate() {
mContext.unregisterReceiver(mModel);
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
launcherApps.removeOnAppsChangedCallback(mModel);
PackageInstallerCompat.getInstance(mContext).onStop();
if (mNotificationBadgingObserver != null) {
mNotificationBadgingObserver.unregister();
}
}
- LauncherModel onPackageAdded 中调用的 enqueueModelUpdateTask 实现
public void enqueueModelUpdateTask(ModelUpdateTask task) {
task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor);
runOnWorkerThread(task);
}
/** Runs the specified runnable immediately if called from the worker thread, otherwise it is
- posted on the worker thread handler. */
private static void runOnWorkerThread(Runnable r) {
if (sWorkerThread.getThreadId() == Process.myTid()) {
r.run();
} else {
// If we are not on the worker thread, then post to the worker handler
sWorker.post®;
}
}
- 上面主要实现是 ModelUpdateTask 在工作线程进行任务处理
/**
- A runnable which changes/updates the data model of the launcher based on certain events.
*/
public interface ModelUpdateTask extends Runnable {
/**
- Called before the task is posted to initialize the internal state.
*/
void init(LauncherAppState app, LauncherModel model,
BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor);
}
public abstract class BaseModelUpdateTask implements ModelUpdateTask {
PackageUpdatedTask 处理由于程序包管理器中的更改(应用程序安装、更新、删除)或用户可用性更改而引起的更新。
/**
Handles updates due to changes in package manager (app installed/updated/removed)
or when a user availability changes.
*/
public class PackageUpdatedTask extends BaseModelUpdateTask {
private static final boolean DEBUG = true;
private static final String TAG = “PackageUpdatedTask”;
public static final int OP_NONE = 0;
public static final int OP_ADD = 1;
public static final int OP_UPDATE = 2;
public static final int OP_REMOVE = 3; // uninstalled
public static final int OP_UNAVAILABLE = 4; // external media unmounted
public static final int OP_SUSPEND = 5; // package suspended
public static final int OP_UNSUSPEND = 6; // package unsuspended
public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
final Context context = app.getContext();
final IconCache iconCache = app.getIconCache();
final String[] packages = mPackages;
final int N = packages.length;
FlagOp flagOp = FlagOp.NO_OP;
final HashSet packageSet = new HashSet<>(Arrays.asList(packages));
ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
switch (mOp) {
case OP_ADD: {
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
iconCache.updateIconsForPkg(packages[i], mUser);
if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
appsList.removePackage(packages[i], Process.myUserHandle());
}
appsList.addPackage(context, packages[i], mUser);
// Automatically add homescreen icon for work profile apps for below O device.
if (!Utilities.ATLEAST_OREO && !Process.myUserHandle().equals(mUser)) {//del by mjf for receive package changed msg
SessionCommitReceiver.queueAppIconAddition(context, packages[i], mUser);
}
}
flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
}
…
final ArrayList addedOrModified = new ArrayList<>();
addedOrModified.addAll(appsList.added);
//add for load new install app on workspace start by lhw
if(FeatureFlags.DISABLE_ALL_APP){
ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
final List profiles = UserManagerCompat.getInstance(context).getUserProfiles();
ArrayList<InstallShortcutReceiver.PendingInstallShortcutInfo> added = new ArrayList<InstallShortcutReceiver.PendingInstallShortcutInfo>();
for (UserHandle user : profiles) {
final List apps = LauncherAppsCompat.getInstance(context).getActivityList(null, user);
synchronized (this) {
for (LauncherActivityInfo info : apps) {
if(DmConfig.isHiddenPackage(info.getComponentName())){continue;}//hide app by lhw
for (AppInfo appInfo : appsList.added) {
if(info.getComponentName().equals(appInfo.componentName)){
InstallShortcutReceiver.PendingInstallShortcutInfo mPendingInstallShortcutInfo
= new InstallShortcutReceiver.PendingInstallShortcutInfo(info,context);
added.add(mPendingInstallShortcutInfo);
installQueue.add(mPendingInstallShortcutInfo.getItemInfo());
}
}
}
}
}
if (!added.isEmpty()) {
app.getModel().addAndBindAddedWorkspaceItems(installQueue);
}
}
//add for load new install app on workspace end by lhw
appsList.added.clear();
addedOrModified.addAll(appsList.modified);
appsList.modified.clear();
final ArrayList removedApps = new ArrayList<>(appsList.removed);
appsList.removed.clear();
final ArrayMap<ComponentName, AppInfo> addedOrUpdatedApps = new ArrayMap<>();
if (!addedOrModified.isEmpty()) {
scheduleCallbackTask(new CallbackTask() {
@Override
public void execute(Callbacks callbacks) {
//TODO bindAppsAddedOrUpdated 通过AllAppsStore的addOrUpdateApps方法去通知AllApps界面刷新 LHW
callbacks.bindAppsAddedOrUpdated(addedOrModified);
}
});
for (AppInfo ai : addedOrModified) {
addedOrUpdatedApps.put(ai.componentName, ai);
}
}
- 通过execute 执行应用的添加 删除 更新操作
- iconCache.updateIconsForPkg 更新应用图标信息
/**
- Updates the entries related to the given package in memory and persistent DB.
*/
public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
removeIconsForPkg(packageName, user);
try {
PackageInfo info = mPackageManager.getPackageInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
long userSerial = mUserManager.getSerialNumberForUser(user);
for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
addIconToDBAndMemCache(app, info, userSerial, false /replace existing/);
}
} catch (NameNotFoundException e) {
Log.d(TAG, “Package not found”, e);
}
}
- 通过appsList.addPackage方法将新应用信息缓存到AllAppsList
/**
- Add the icons for the supplied apk called packageName.
*/
public void addPackage(Context context, String packageName, UserHandle user) {
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
final List matches = launcherApps.getActivityList(packageName,
user);
for (LauncherActivityInfo info : matches) {
add(new AppInfo(context, info, user), info);
}
}
/**
Add the supplied ApplicationInfo objects to the list, and enqueue it into the
list to broadcast when notify() is called.
If the app is already in the list, doesn’t add it.
*/
public void add(AppInfo info, LauncherActivityInfo activityInfo) {
if (!mAppFilter.shouldShowApp(info.componentName)) {
return;
}
if (findAppInfo(info.componentName, info.user) != null) {
return;
}
mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */);
data.add(info);
added.add(info);
}
- 通过bindAppsAddedOrUpdated方法回调launcher,进行界面更新的处理。
final ArrayMap<ComponentName, AppInfo> addedOrUpdatedApps = new ArrayMap<>();
if (!addedOrModified.isEmpty()) {
scheduleCallbackTask(new CallbackTask() {
@Override
public void execute(Callbacks callbacks) {//TODO bindAppsAddedOrUpdated 通过AllAppsStore的addOrUpdateApps方法去通知AllApps界面刷新 LHW
callbacks.bindAppsAddedOrUpdated(addedOrModified);
}
});
for (AppInfo ai : addedOrModified) {
addedOrUpdatedApps.put(ai.componentName, ai);
}
}
4.Launcher.java 中通过AllAppsStore的addOrUpdateApps方法去通知AllApps界面刷新
/**
- Default launcher application.
*/
public class Launcher extends BaseDraggingActivity implements LauncherExterns,
LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate{
public static final String TAG = “Launcher”;
/**
A package was updated.
Implementation of the meth
od from LauncherModel.Callbacks.
*/
@Override
public void bindAppsAddedOrUpdated(ArrayList apps) {
mAppsView.getAppsStore().addOrUpdateApps(apps);
}
5.在AllAppsStore中更新App 列表
/**
- A utility class to maintain the collection of all apps.
*/
public class AllAppsStore {
/**
- Adds or updates existing apps in the list
*/
public void addOrUpdateApps(List apps) {
for (AppInfo app : apps) {
Log.d(“LHW_L”,“addOrUpdateApps-==”+app.title+",=apps.size="+apps.size());
mComponentToAppMap.put(app.toComponentKey(), app);
}
notifyUpdate();
}
- AllAppsContainerView 获取 AllAppsStore 所有应用数据,通过all_apps_rv_layout.xml中AllAppsRecyclerView显示
/**
- The all apps view container.
*/
public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
Insettable, OnDeviceProfileChangeListener {
private final AllAppsStore mAllAppsStore = new AllAppsStore();
public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mLauncher.addOnDeviceProfileChangeListener(this);
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
mAH = new AdapterHolder[2];
mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
mNavBarScrimPaint = new Paint();
mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
mAllAppsStore.addUpdateListener(this::onAppsUpdated);
addSpringView(R.id.all_apps_header);
addSpringView(R.id.apps_list_view);
addSpringView(R.id.all_apps_tabs_view_pager);
}
- AllAppsContainerView中 AdapterHolder 的 AllAppsGridAdapter加载 显示所有App信息到视图
/**
- A RecyclerView with custom fast scroll support for the all apps view.
*/
public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
public class AdapterHolder {
public static final int MAIN = 0;
public static final int WORK = 1;
public final AllAppsGridAdapter adapter;
final LinearLayoutManager layoutManager;
final AlphabeticalAppsList appsList;
final Rect padding = new Rect();
AllAppsRecyclerView recyclerView;
boolean verticalFadingEdge;
AdapterHolder(boolean isWork) {
appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
adapter = new AllAppsGridAdapter(mLauncher, appsList);
appsList.setAdapter(adapter);
layoutManager = adapter.getLayoutManager();
}
void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
appsList.updateItemFilter(matcher);
recyclerView = (AllAppsRecyclerView) rv;
recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
recyclerView.setApps(appsList, mUsingTabs);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
// No animations will occur when changes occur to the items in this RecyclerView.
recyclerView.setItemAnimator(null);
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
recyclerView.addItemDecoration(focusedItemDecorator);
adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
applyVerticalFadingEdgeEnabled(verticalFadingEdge);
applyPadding();
}
- AllAppsGridAdapter 获取数据显示App 图标和信息
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout());
icon.setOnFocusChangeListener(mIconFocusListener);
// Ensure the all apps icon height matches the workspace icons in portrait mode.
icon.getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
return new ViewHolder(icon);
all_apps_icon.xml 中 BubbleTextView 显示桌面图标信息
<com.android.launcher3.BubbleTextView
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:launcher=“http://schemas.android.com/apk/res-auto”
style="@style/BaseIcon"
android:id="@+id/icon"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:stateListAnimator="@animator/all_apps_fastscroll_icon_anim"
launcher:iconDisplay=“all_apps”
launcher:centerVertically=“true”
android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
android:paddingRight="@dimen/dynamic_grid_cell_padding_x" />
onBindViewHolder applyFromApplicationInfo 填充App信息数据
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case VIEW_TYPE_ICON:
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.reset();
icon.applyFromApplicationInfo(info);
break;
Launcher 自定义 BubbleTextView 显示应用图标和信息
/**
TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
because we want to make the bubble taller than the text and TextView’s clip is
too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback {
public void applyFromApplicationInfo(AppInfo info) {
applyIconAndLabel(info);
// We don’t need to check the info since it’s not a ShortcutInfo
super.setTag(info);
// Verify high res immediately
verifyHighRes();
if (info instanceof PromiseAppInfo) {
PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info;
applyProgressLevel(promiseAppInfo.level);
}
applyBadgeState(info, false /* animate */);
}
private void applyIconAndLabel(ItemInfoWithIcon info) {
FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(info);
mBadgeColor = IconPalette.getMutedColor(info.iconColor, 0.54f);
setIcon(iconDrawable);
setText(info.title);
if (info.contentDescription != null) {
setContentDescription(info.isDisabled()
- ? getContext().getString(R.string.disabled_app_label, info.contentDescription)
- info.contentDescription);
}
}
/**
- Sets the icon for this view based on the layout direction.
*/
private void setIcon(Drawable icon) {
if (mIsIconVisible) {
applyCompoundDrawables(icon);
}
mIcon = icon;
}
protected void applyCompoundDrawables(Drawable icon) {
// If we had already set an icon before, disable relayout as the icon size is the
// same as before.
mDisableRelayout = mIcon != null;
icon.setBounds(0, 0, mIconSize, mIconSize);
if (mLayoutHorizontal) {
setCompoundDrawablesRelative(icon, null, null, null);
} else {
setCompoundDrawables(null, icon, null, null);
}
mDisableRelayout = false;
}
Launcher 初始加载所有应用信息
Launcher.java文件 在onCreate 通过 LauncherModel 的 startLoader 函数进行数据的绑定加载和更新
/**
- Default launcher application.
*/
public class Launcher extends BaseDraggingActivity implements LauncherExterns,
LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate{
public static final String TAG = “Launcher”;
static final boolean LOGD = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
TraceHelper.beginSection(“Launcher-onCreate”);
super.onCreate(savedInstanceState);
TraceHelper.partitionSection(“Launcher-onCreate”, “super call”);
LauncherAppState app = LauncherAppState.getInstance(this);
mOldConfig = new Configuration(getResources().getConfiguration());
//获取初始化 LauncherModel 进行广播监听数据绑定和更新加载
mModel = app.setLauncher(this);
initDeviceProfile(app.getInvariantDeviceProfile());
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
int currentScreen = PagedView.INVALID_RESTORE_PAGE;
if (savedInstanceState != null) {
currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
}
// LauncherModel startLoader 开启线程进行数据加载
if (!mModel.startLoader(currentScreen)) {
if (!internalStateHandled) {
// If we are not binding synchronously, show a fade in animation when
// the first page bind completes.
mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
}
} else {
// Pages bound synchronously.
mWorkspace.setCurrentPage(currentScreen);
setWorkspaceLoading(true);
}
// For handling default keys
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
setContentView(mLauncherView);
LauncherModel 广播监听Launcher数据处理和接口回调
startLoader() 绑定和加载所用应用信息、小组件、快捷方式等到Workspace
/**
Maintains in-memory state of the Launcher. It is expected that there should be only one
LauncherModel object held in a static. Also provide APIs for updating the database state
for the Launcher.
*/
public class LauncherModel extends BroadcastReceiver
implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
private static final boolean DEBUG_RECEIVER = !com.android.launcher3.Log.IS_USER_VERSON;//true;
/**
Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
@return true if the page could be bound synchronously.
*/
public boolean startLoader(int synchronousBindPage) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
synchronized (mLock) {
// Don’t bother to start the thread if we know it’s not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
final Callbacks oldCallbacks = mCallbacks.get();
// Clear any pending bind-runnables from the synchronized load process.
// begin modify by mjf for REQ006
//mUiExecutor.execute(oldCallbacks::clearPendingBinds);
final Runnable runnable = new Runnable() {
@Override
public void run() {
oldCallbacks.clearPendingBinds();
}
};
mUiExecutor.execute(runnable);
// end
// If there is already one running, tell it to stop.
stopLoader();
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
mBgAllAppsList, synchronousBindPage, mCallbacks);
if (mModelLoaded && !mIsLoaderTaskRunning) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
loaderResults.bindWorkspace();
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
startLoaderForResults(loaderResults);
}
}
}
return false;
}
- startLoaderForResults() 开始加载和返回数据结果
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
runOnWorkerThread(mLoaderTask);
//mUiExecutor.execute(oldCallbacks::clearPendingBinds);
final Runnable runnable = new Runnable() {
@Override
public void run() {
oldCallbacks.clearPendingBinds();
}
};
mUiExecutor.execute(runnable);
// end
// If there is already one running, tell it to stop.
stopLoader();
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
mBgAllAppsList, synchronousBindPage, mCallbacks);
if (mModelLoaded && !mIsLoaderTaskRunning) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
loaderResults.bindWorkspace();
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
startLoaderForResults(loaderResults);
}
}
}
return false;
}
- startLoaderForResults() 开始加载和返回数据结果
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
runOnWorkerThread(mLoaderTask);
Launcher3 安装App加载显示过程分析,androidstudio开发app实例相关推荐
- 百度地图调用加载显示Marker,并添加点击事件
百度地图调用加载显示Marker,并添加点击事件 注册百度开发者账号,申请应用AK 百度地图开发平台官网 点击右上角控制台,选择创建应用 创建应用,勾选浏览器端,白名单填写* 注:如上线更改为公网IP ...
- Python机器视觉--OpenCV入门--OpencCV的安装与图片加载显示
1. 安装OpenCV 执行pip install opencv-python==3.4.1.15, 3.4.2之后有些算法申请了专利,用不了了.(使用其他版本也可以) 安装opencv扩展包(选装) ...
- Ionic+Angular+Express实现前后端交互使用HttpClient发送get请求数据并加载显示(附代码下载)
场景 Ionic介绍以及搭建环境.新建和运行项目: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/106308166 在上面搭建起 ...
- 从app加载页面说开去
好的交互设计的评判标准之一是"别让我等",但互联网产品总是受制于实际的网络问题.移动端产品则更为明显,2G.3G环境下加载不够给力,wifi环境也未必每次都是那么顺利.因此&quo ...
- 唯一插件化Replugin源码及原理深度剖析--插件的安装、加载原理
上一篇 唯一插件化Replugin源码及原理深度剖析–唯一Hook点原理 在Replugin的初始化过程中,我将他们分成了比较重要3个模块,整体框架的初始化.hook系统ClassLoader.插件的 ...
- 免Root 实现App加载Xposed插件的工具Xpatch源码解析(一)
前言 Xpatch是一款免Root实现App加载Xposed插件的工具,可以非常方便地实现App的逆向破解(再也不用改smali代码了),源码也已经上传到Github上,欢迎各位Fork and St ...
- 【Android开发那点破事】打开APP加载页面实现
今天的破事呢就说说APP加载页面的实现.一般情况下,当APP打开的时候,我们需要做很多事情,比如检查网络连接啊,初始化一些配置啊等等.我们可以让这些事情在APP完全打开之前做完,然后呢在打开的过程中显 ...
- Android端加载显示高分辨率卫星地图
Android端加载显示高分辨率卫星地图 *哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 * 使用天地图卫星地图在arcgis环境下加载高清卫星地图,该部分仅简单提供成功加载显示天地图卫星图层 ...
- three.js 加载显示文字
three.js 加载显示文字 代码放到 ./three.js/examples/ 下 <!DOCTYPE html> <html lang="en">&l ...
最新文章
- 红旗桌面版本最新运用要领和成果解答100例-7
- preCornerDetect函数
- Xss-labs闯关总结
- [翻译]现代Linux系统上的栈溢出攻击【转】
- return print
- java数组如何相加_java数组排序,并将数组内的数据求和
- python静态方法怎么调用_在python中调用静态方法
- some any oracle,Oracle之 any、some、all 解析
- 什么是pisa测试_PISA测试排名世界第一,中国教育已是世界冠军?
- python通配符搜索文件_Python 如何查找特定类型文件
- SeleniumCSS选择器
- 【Oracle】基础知识查漏补缺
- Cheat Engine CE官方教程 [汉化]
- [史]《全球通史》上册——摘记
- NLTK2:词性标注
- OSError: inotify watch limit reached
- 计算机课代表中段考总结,第一学期中段考试总结
- 微信小程序如何实现切换主题(更改皮肤)
- IntelliJ IDEA如何修改版权信息
- 心态-《不抱怨的世界》书中的精髓:通过4个步骤,成为一个不抱怨、每天都快乐的人。