程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 淺析Java中對象的創立與對象的數據類型轉換

淺析Java中對象的創立與對象的數據類型轉換

編輯:關於JAVA

淺析Java中對象的創立與對象的數據類型轉換。本站提示廣大學習愛好者:(淺析Java中對象的創立與對象的數據類型轉換)文章只能為提供參考,不一定能成為您想要的結果。以下是淺析Java中對象的創立與對象的數據類型轉換正文


activity的啟動流程

加載一個Activity確定不會像加載普通的類那樣,由於activity作為體系的組件有本身的性命周期,有體系的許多回調掌握,所以自界說一個DexClassLoader類加載器來加載插件中的Activity確定是弗成以的。

起首不能不懂得一下activity的啟動流程,固然只是簡略的看一下,太具體的話很難研討清晰。

經由過程startActivity啟動後,終究經由過程AMS停止跨過程回調到ApplicationThread的scheduleLaunchActivity,這時候會創立一個ActivityClientRecord對象,這個對象表現一個Acticity和他的相干信息,好比activityInfo字段包含了啟動形式等,還有loadedApk,望文生義指的是加載過了的APK,他會被放在一個Map中,運用包名到LoadedApk的鍵值對,包括了一個運用的相干信息。然後經由過程Handler切換到主線程執performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
// 1.創立ActivityClientRecord對象時沒有對他的packageInfo賦值,所以它是null
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE);
}
// ...
Activity activity = null;
try {
// 2.異常主要!!這個ClassLoader保留於LoadedApk對象中,它是用來加載我們寫的activity的加載器
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
// 3.用加載器來加載activity類,這個會依據分歧的intent加載婚配的activity
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) {
// 4.這裡的異常也長短常異常主要的!!!前面就依據這個提醒找到沖破口。。。
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
// 從這裡就會履行到我們平日看到的activity的性命周期的onCreate外面
mInstrumentation.callActivityOnCreate(activity, r.state);
// 省略的是依據分歧的狀況履行性命周期
}
r.paused = true;
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
// ...
}
return activity;
}

1.getPackageInfo辦法終究前往一個LoadedApk對象,它會從一個HashMap的數據構造中取,mPackages保護了包名和LoadedApk的對應關系,即每個運用有一個鍵值對對應。假如為null,就新創立一個LoadedApk對象,並將其添加到Map中,重點是這個對象的ClassLoader字段為null!

public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
int flags) {
// 為true
boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
boolean securityViolation = includeCode && ai.uid != 0
&& ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
: true);
// ...
// includeCode為true
// classloader為null!!!
return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
synchronized (mPackages) {
WeakReference<loadedapk> ref;
if (includeCode) {
// includeCode為true
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) // ...
// packageInfo為null,創立一個LoadedApk,而且添加到mPackages外面
packageInfo = new LoadedApk(this, aInfo, compatInfo, this, baseLoader, securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo. ) != 0);
if (includeCode) {
mPackages.put(aInfo.packageName, new WeakReference<loadedapk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName, new WeakReference<loadedapk>(packageInfo));
}
}
return packageInfo;
}
}</loadedapk></loadedapk></loadedapk>

2.獲得這個activity對應的類加載器,因為下面說過,mClassLoader為null,那末就會履行到ApplicationLoaders#getClassLoader(zip, libraryPath, mBaseClassLoader)辦法。

public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader != null) {
return mClassLoader;
}
// ...
// 創立加載器,創立默許的加載器
// zip為Apk的途徑,libraryPath也就是JNI的途徑
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, libraryPath, mBaseClassLoader);
initializeJavaContextClassLoader();
StrictMode.setThreadPolicy(oldPolicy);
} else {
if (mBaseClassLoader == null) {
mClassLoader = ClassLoader.getSystemClassLoader();
} else {
mClassLoader = mBaseClassLoader;
}
}
return mClassLoader;
}
}

ApplicationLoaders應用單例它的getClassLoader辦法依據傳入的zip途徑現實上也就是Apk的途徑來創立加載器,前往的是一個PathClassLoader。而且PathClassLoader只能加載裝置過的APK。這個加載器創立的時刻傳入的是以後運用APK的途徑,理所應該的,想加載其他的APK就結構一個傳遞其他APK的類加載器。

3.用該類加載器加載我們要啟動的activity,並反射創立一個activity實例

public Activity newActivity(ClassLoader cl, String className,Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}

總結一下下面的思緒就是,當我們啟動一個activity時,經由過程體系默許的PathClassLoader來加載這個activity,固然默許情形下只能加載本運用外面的activity,然後就由體系挪用到這個activity的性命周期中。

4.這個處所的異常在前面的示例中會湧現,到時刻剖析到緣由後便可以找出我們靜態加載Activity的思緒了。

靜態加載Activity:修正體系類加載器

依照這個思緒,做如許的一個示例,按下按鈕,翻開插件中的Activity。

插件項目

plugin.dl.pluginactivity

|--MainActivity.java

內容很簡略,就是一個結構下面寫了這是插件中的Activity!偏重寫了他的onStart和onDestroy辦法。

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加載到宿主法式中以後,這個R.layout.activity_main就是宿主法式中的R.layout.activity_main了
setContentView(R.layout.activity_main);
}
@Override
protected void onStart() {
super.onStart();
Toast.makeText(this,"onStart", 0).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
Toast.makeText(this,"onDestroy", 0).show();
}
}

宿主項目

host.dl.hostactivity

|--MainActivity.java

包含兩個按鈕,第一個按鈕跳轉到插件中的MainActivity.java,第二個按鈕調轉到本運用中的MainActivity.java

private Button btn;
private Button btn1;
DexClassLoader loader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
btn1 = (Button) findViewById(R.id.btn1);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Class activity = null;
String dexPath = "/PluginActivity.apk";
loader = new DexClassLoader(dexPath, MainActivity.this.getApplicationInfo().dataDir, null, getClass().getClassLoader());
try {
activity = loader.loadClass("plugin.dl.pluginactivity.MainActivity");
}catch (ClassNotFoundException e) {
Log.i("MainActivity", "ClassNotFoundException");
}
Intent intent = new Intent(MainActivity.this, activity);
MainActivity.this.startActivity(intent);
}
});
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, MainActivity2.class);
MainActivity.this.startActivity(intent);
}
});

起首我們要將該activity在宿主工程的額AndroidManifest外面注冊。點擊按鈕翻開插件中的activity,發明報錯

java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{host.dl.hostactivity/plugin.dl.pluginactivity.MainActivity}: java.lang.ClassNotFoundException: plugin.dl.pluginactivity.MainActivity

#曾經應用自界說的加載器,當startActivity時為何提醒找不到插件中的activity?

後面第四點說過這個異常。其實這個異常就是在performLaunchActivity中拋出的,細心看這個異常打印信息,發明它說plugin.dl.pluginactivity.MainActivity類找不到,可是我們不是方才界說了一個DexClassLoader,勝利加載了這個類的嗎??怎樣這裡又提醒這個類找不到?

現實上,確切是如許的,還記得後面說過,體系默許的類加載器PathClassLoader嗎?(由於LoadedApk對象的mClassLoader變量為null,就挪用到ApplicationLoaders#getClassLoader辦法,即依據以後運用的途徑前往一個默許的PathClassLoader),當履行到mPackages.get(aInfo.packageName);時從Map獲得的LoadedApk中未指定mClassLoader,是以會應用體系默許的類加載器。因而當履行這一句 mInstrumentation.newActivity(cl, component.getClassName(), r.intent);時,因為這個類加載器找不到我們插件工程中的類,是以報錯了。

如今很清晰了,緣由就是應用體系默許的這個類加載器不包括插件工程途徑,沒法准確加載我們想要的activity形成的。

因而斟酌調換體系的類加載器。

private void replaceClassLoader(DexClassLoader loader) {
try {
Class clazz_Ath = Class.forName("android.app.ActivityThread");
Class clazz_LApk = Class.forName("android.app.LoadedApk");
Object currentActivityThread = clazz_Ath.getMethod("currentActivityThread").invoke(null);
Field field1 = clazz_Ath.getDeclaredField("mPackages");
field1.setAccessible(true);
Map mPackages = (Map) field1.get(currentActivitead);
String packageName = MainActivity.this.getPackageName();
WeakReference ref = (WeakReference) mPackages.get(packageName);
Field field2 = clazz_LApk.getDeclaredField("mClassLoader");
field2.setAccessible(true);
field2.set(ref.get(), loader);
} catch (Exception e) {
e.printStackTrace();
}
}

這段代碼的思緒是將ActivityThread類中的mPackages變量中保留的以以後包名為鍵的LoadedApk值的mClassLoader調換成我們自界說的類加載器。當下一主要加載寄存在其余處所的插件中的某個Activity時,直接在mPackages變量中能取到,是以用的就是我們修正了的類加載器了。
是以,在翻開插件中的activity之前挪用replaceClassLoader(loader);辦法調換體系的類加載器,便可以了。

後果以下


此時發明可以啟動插件中的activity,由於履行到了他的onStart辦法,而且封閉的時刻履行了onDestroy辦法,然則奇異的是界面上的控件貌似沒有變更?和啟動他的界面如出一轍,還不克不及點擊。這是甚麼緣由呢?

明顯,我們只是把插件中的MainActivity類加載過去了,當履行到他的onCreate辦法時,在外面挪用setContentView應用的結構參數是R.layout.activity_main,固然應用的就是以後運用的資本了!

##曾經調換了體系的類加載器為何加載本運用的activity卻能正常運轉?

不外在修改這個成績之前,有無發明一個很奇異的景象,當加載過插件中的activity後,再次啟動當地的activity也是能正常啟動的?這是為何呢?後面曾經調換了默許的類加載器了,而且可以在翻開插件中的activity後再點擊第二個按鈕翻開本運用的activity之前檢查應用的activity,確切是我們曾經調換了的類加載器。那這裡為何還能正常啟動本運用的activity呢?玄機就在我們創立DexClassLoader時的第四個參數,父加載器!設置父加載器為以後類的加載器,就可以包管類的雙親委派模子不被損壞,在加載類時都是先由父加載器來加載,加載不勝利時在由本身加載。不信可以在new這個加載器的時刻父加載器的參數設置成其他值,好比體系類加載器,那末當運轉activity時確定會報錯。

接上去處理後面湧現的,跳轉到插件activity中界面顯示纰謬的成績。這個景象湧現的緣由曾經說明過了,就是由於應用了當地的資本所招致的,是以須要在setContentView時,應用插件中的資本結構。是以在插件Activity中作以下修正

public class MainActivity2 extends Activity {
private static View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加載到宿主法式中以後,這個R.layout.activity_main就是宿主法式中的R.layout.activity_main了
// setContentView(R.layout.activity_main);
if (view != null)
setContentView(view);
}
@Override
protected void onStart() {
super.onStart();
Toast.makeText(this,"onStart", 0).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
Toast.makeText(this,"onDestroy", 0).show();
}
private static void setLayout(View v){
view = v;
}
}

然後在宿主Activity中獲得插件資本並將結構填充成View,然後設置給插件中的activity,作為它的ContentView的內容。

Class<!--?--> layout = loader.loadClass("plugin.dl.pluginactivity.R$layout");
Field field = layout.getField("activity_main");
Integer obj = (Integer) field.get(null);
// 應用包括插件APK的Resources對象來獲得這個結構能力准確獲得插件中界說的界面後果
//View view = LayoutInflater.from(MainActivity.this).inflate(resources.getLayout(obj),null);
// 或許如許,但必定要重寫getResources辦法,能力如許寫
View view = LayoutInflater.from(MainActivity.this).inflate(obj, null);
Method method = activity.getDeclaredMethod("setLayout", View.class);
method.setAccessible(true);
method.invoke(activity, view);

完全的代碼

public class MainActivity extends Activity {
private Resources resources;
protected AssetManager assetManager;
private Button btn;
private Button btn1;
DexClassLoader loader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
btn1 = (Button) findViewById(R.id.btn1);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String dexPath = "/PluginActivity.apk";
loader = new DexClassLoader(dexPath, MainActivity.this.getApplicationInfo().dataDir, null, getClass().getClassLoader());
Class<!--?--> activity = null;
Class<!--?--> layout = null;
try {
activity = loader.loadClass("plugin.dl.pluginactivity.MainActivity");
layout = loader.loadClass("plugin.dl.pluginactivity.R$layout");
}catch (ClassNotFoundException e) {
Log.i("MainActivity", "ClassNotFoundException");
}
replaceClassLoader(loader);
loadRes(dexPath);
try {
Field field = layout.getField("activity_main");
Integer obj = (Integer) field.get(null);
// 應用包括插件APK的Resources對象來獲得這個結構能力准確獲得插件中界說的界面後果
View view = LayoutInflater.from(MainActivity.this).inflate(resources.getLayout(obj),null);
// 或許如許,但必定要重寫getResources辦法,能力如許寫
// View view = LayoutInflater.from(MainActivity.this).inflate(obj, null);
Method method = activity.getDeclaredMethod("setLayout", View.class);
method.setAccessible(true);
method.invoke(activity, view);
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent(MainActivity.this, activity);
MainActivity.this.startActivity(intent);
}
});
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, MainActivity2.class);
MainActivity.this.startActivity(intent);
}
});
}
public void loadRes(String path){
try {
assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, path);
} catch (Exception e) {
}
resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
// 也能夠依據資本獲得主題
}
private void replaceClassLoader(DexClassLoader loader){
try {
Class clazz_Ath = Class.forName("android.app.ActivityThread");
Class clazz_LApk = Class.forName("android.app.LoadedApk");
Object currentActivityThread = clazz_Ath.getMethod("currentActivityThread").invoke(null);
Field field1 = clazz_Ath.getDeclaredField("mPackages");
field1.setAccessible(true);
Map mPackages = (Map)field1.get(currentActivityThread);
String packageName = MainActivity.this.getPackageName();
WeakReference ref = (WeakReference) mPackages.get(packageName);
Field field2 = clazz_LApk.getDeclaredField("mClassLoader");
field2.setAccessible(true);
field2.set(ref.get(), loader);
} catch (Exception e){
System.out.println("-------------------------------------" + "click");
e.printStackTrace();
}
}
@Override
public Resources getResources() {
return resources == null ? super.getResources() : resources;
}
@Override
public AssetManager getAssets() {
return assetManager == null ? super.getAssets() : assetManager;
}
}

靜態加載Activity:應用署理

還有一種方法啟動插件中的activity的方法就是將插件中的activity當作一個普通的類,不把它當做組件activity,因而在啟動的時刻啟動一個署理ProxyActivity,它才是真實的Activity,他的性命周期由體系治理,我們在它外面挪用插件Activity裡的函數便可。同時,在插件Activity外面保留一個署理Activity的援用,把這個援用當作高低文情況Context懂得。

這裡插件Activity的性命周期函數均由署理Activity調起,ProxyActivity其實就是一個真實的我們啟動的Activity,而不是啟動插件中的Activity,插件中的“要啟動”的Activity就當作一個很通俗的類對待,當做一個包括了一些函數的通俗類來懂得,只是這個類外面的函數名字起的有些“奇異”而已。觸及到拜訪資本和更新UI相干的時刻經由過程以後高低文情況,即保留的proxyActivity援用來獲得。

以上面這個Demo為例

宿主項目

com.dl.host

|--MainActivity.java

|--ProxyActivity.java

MainActivity包含一個按鈕,按下按鈕跳轉到插件Activity

public class MainActivity extends Activity{
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button)findViewById(R.id.btn);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
MainActivity.this.startActivity(new Intent(MainActivity.this, ProxyActivity.class));
}
});
}
}

ProxyActivity就是我們要啟動的插件Activity的一個傀儡,署理。是體系保護的Activity。

public class ProxyActivity extends Activity{
private DexClassLoader loader;
private Activity activity;
private Class<!--?--> clazz = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loader = new DexClassLoader("/Plugin.apk", getApplicationInfo().dataDir, null, getClass().getClassLoader());
try {
clazz = loader.loadClass("com.dl.plugin.MainActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 設置插件activity的署理
try {
Method setProxy = clazz.getDeclaredMethod("setProxy", Activity.class);
setProxy.setAccessible(true);
activity = (Activity)clazz.newInstance();
setProxy.invoke(activity, this);
Method onCreate = clazz.getDeclaredMethod("onCreate", Bundle.class);
onCreate.setAccessible(true);
onCreate.invoke(activity, savedInstanceState);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onStart() {
super.onStart();
// 挪用插件activity的onStart辦法
Method onStart = null;
try {
onStart = clazz.getDeclaredMethod("onStart");
onStart.setAccessible(true);
onStart.invoke(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onStart();
// 挪用插件activity的onDestroy辦法
Method onDestroy = null;
try {
onDestroy = clazz.getDeclaredMethod("onDestroy");
onDestroy.setAccessible(true);
onDestroy.invoke(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}

可以看到,ProxyActivity其實就是一個真實的Activity,我們啟動的就是這個Activity,而不是插件中的Activity。

插件項目

com.dl.plugin

|--MainActivity.java

保留了一個署理Activity的援用,值得留意的是,因為拜訪插件中的資本須要額定的操作,要加載資本,是以這裡未應用插件項目外面的資本,所以我應用代碼添加的TextView,但道理和後面講的內容是一樣的。

public class MainActivity extends Activity {
private Activity proxyActivity;
public void setProxy(Activity proxyActivity) { 
this.proxyActivity = proxyActivity;
}
// 外面的一切操作都由署理activity來操作
@Override
protected void onCreate(Bundle savedInstanceState) {
TextView tv = new TextView(proxyActivity);
tv.setText("插件Activity");
proxyActivity.setContentView(tv,new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
@Override
protected void onStart() {
Toast.makeText(proxyActivity, "插件onStart", 0).show();
}
@Override
protected void onDestroy() {
Toast.makeText(proxyActivity, "插件onDestroy", 0).show();
}
}

這類辦法比擬較後面修正體系加載器的辦法須要本身保護性命周期,比擬費事,前一種方法由體系本身保護,而且啟動的就是插件中實其實在的Activity。

前一種方法要在宿主的AndroidManifest外面聲明插件Activity,如許當activity太多時就要聲明許多,比擬繁瑣,不外也能夠不聲明逃過體系檢討。前面這類方法就只須要一個署理ProxyActivity類便可。在他的onCreate外面依據傳遞的值選擇加載插件中的哪一個Activity便可。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved